Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

воскресенье, 6 марта 2011 г.

Java for fun: #2 Пишем Web проект на Java в Eclipse для Tomcat. Билдим Ant. Проверяем Hudson. Тестим jUnit + EasyMock + jWebUnit. Коммитим в Svn.

В первой части я расказал как настроить самое основное окружение, а тут мы уже начнем реализовывать web приложение. Сегодня у нас будет готовая login page. Пристегнись это займет много твоего времени. Читаем дальше...

Нам надо выкачать jWebUnit. Эта приблуда используется для  функционального тестирования вебморды твоего будущего приложения...



Библиотеку распаковываем в одноименную папку в проекте (в папке lib)...


Настраиваем библиотеки в Eclipse. Немного муторное занятие, но только в первые разы...

Создаем структуру папок. необходимую для нормальной работы web приложения....



Создаем файлик MANIFEST.MF...



Дело в том, что я немного ошибся раньше и мы создали не тот тип проекта. Чтобы сейчас все не переделывать по новому я предлагаю пойти на маленькую хитрость. Идея в том, чтобы создать рядом с нашим проектом новый пустой проект необходимого типа "Dynamic Web Project", а потом переписать все его Eclipse настройки в существующий проект и перезапустить Eclipse.

Итак создаем новый "Dynamic Web Project" проект...




Закрываем наш текущий проект, чтобы была возможность перезаписать его Eclipse-настройки...


Переписываем эти самые Eclipse-настройки....

В некоторых файлах правим абсолютные пути...



Дописываем сердце web-проекта - файл web.xml ...


В нем тоже правим название проекта...


Открываем наш, уже модифицированный, проект...


И наблюдаем что Eclipse опознал его как web-проект ...


Настраиваем web библиотеки...


Подключаем jUnit библиотеки...


Создаем первый класс. В этом классе мы опишем первые и самые примитивные требования к нашему новому приложению. Это требование необходимо сейчас для того, чтобы позже знать - реализовали ли мы его правильно или нет, а в виде кода оно пишется потому, что я очень ленивый и мне лень будет запускать руками приложение в брайзере всякий раз, как понадобится проверить соответствует ли оно этому требованию...


Так же я настоятельно рекомендую скачать другую версию Eclipse (та, что мы качали ранее невыносимо тормозит)... Спасибо leocom за совет!



Еще нам надо скачать jetty server. Jetty - это приблуда в контейнере которой запускается наше приложение при тестировании его с помощью jWebUnit...





Из предлагаемого архиванам надо всего лишь три библиотеки. Перепишем их в одноименную папку....


Настроим в Eclipse новые jar библиотеки...


Напишем наконец наше требование. Еслтественно после его запуска как jUnit тест, требование не пройдет - у нас нет еще пока того, что мы требуем. Но тем временем мы уже знаем, что требование работает (проверяет что приложение пока не работает)...


Совсем забыл сказать как запускать все требования (или тесты) в проекте...


Теперь создадим класс контроллера, который будет простым Servlet, отвечающим на запросы пользователя из браузера...


Для тестирования Servlet нам понадобится EasyMock библиотека. Это та библиотека, без которой говорить про unit-тестирование не приходится - она позволяет тестировать модули приложения независимо один от другого...


Уже знакомое распаковывание библиотеки в папку lib проекта...


Еще нам понадобится вспомогательные библиотеки - библиотеки для библиотек ...



Еще одна...




И еще две. Предлагаю как самостоятельное задание найти их самостоятельно в сети....


Так же самостоятельно добавишь их в проект в Eclipse - мы это делали раньше не однократно...



Вот как сейчас выглядит структура нашего web-приложения...


Сердце его - файл web-xml должен быть таким...


Класс с описанием требований выглядит так...


Шаблон странички, которая будет отображаться пользователю - jsp файл выгляди так...


Контроллер - сервлет отвечающий за обработку действий странички...


Если мы немного похимичим, то сможем уже сейчас (без Tomcat) увидеть как выглядит в браузере наше приложение. Для этого стоит в классе с требованиями сделать так...


А после скопировать порт в урл, который вставить в адресную строку браузера...


Мы сейчас только что руками проделали поведение нашего требования, описанного в LoginPage.java. Это я сделал один раз, чтобы проверить, что требование работает нормально и больше возвращаться к нему не буду - теперь у меня есть автоматизированный код, который сделает это за меня. Напомню что хороший программист - ленив, и по этому мы запрограммировали это требование в коде.

Когда был студентом, работал по принципу "сейчас запустим посмотрим". Так я делал всякий раз, когда вносились изменения. Подход работает, об этом свидетельствуют пару десятков программ разработанные мной в то время, но такой подход подразумевает игнорирование ошибок внесенных с каждым изменением - просто невозможно после исправления одной строчки все опять руками протестировать.

А требование простое - при вызове приложения должна отобразиться login форма. Вот и вся премудрость. Запустим его...


Зеленый цвет - требование прошло. Красный - нет. Так как ошибиться можно и при написании требованиия - желательно, чтобы каждое твое требование хоть раз проходило через "красную полосу" с сообщением, которое говорит о проваеле приложением именно этого требования. Часто бывает, что ошибка в требовании, а видно это так же как и при провале требования - через "красную полосу", с одним лишь отличием - сообщение...

Но вернемся к нашему браузеру...


Страничка естественно еще не обрабатывает нажатие на кнопку Login...


Но это нам сейчас и не надо. Главное, что наше приложение уже хоть что-то делает. На этом предлагаю сохраниться. Незнаю как ты, но я не люблю игры в которых подолгу нельзя сохраниться. Программирование - игра в которой сохранение сожно делать так часто, насколько это надо. Тут нам поможет TortoiseSVN...


Выбираем файлы, которые необходимо сохранить; пишем сообщение для себя в будущем - что это за версия такая (поверь, в будущем ты будешь читать это)...


На сим можно сделать перерыв и перевести дыхание...

Следующим шагом нам надо немного исправить структуру проекта. Я как всегда допустил ошибку. Готовься, ошибок будет очень много как у меня так и у тебя. Программист не делающий ошибки - никогда не кодил. Именно по этой причине в ходе разработки нашего web-приложения мы будем как можно больше действий автоматизировать - то, что делает компьютер менее подвержено ошибкам. Ошибка заключается в том, что jsp-странички должны находится в папке WebContent а не WebContent/WEB-INF. Перенесем их простым Drag&Drop файлов в структуре проекте.

Итак нам надо добавить новую jsp-страничку которая будет отвечать за страничку приветствия залогированного пользователя. На этом этапе сделаем так, чтобы страничка приветствия отображала данные, введенные пользователем, как только он нажмет на кнопку Login на login страничке.

Так же мы создадим новый unit тест для LoginController. Юнит тестом тест называется потому, что он будет тестировать модуль-класс (LoginController) независимо от других модулей, которые используются в нем. Работу всех этих модулей эмулирует библиотека EasyMock.


А вот немного стрелочек описывающих взаимосвязи в простом web-приложении. Попробуй разобраться самостоятельно. Тут же подгляди и исходные коды некоторых файлов...


Вот еще парочка исходников, которые не попали на диаграмму. Я привожу скриншоты специально для того, чтобы нельзя было скопировать текст и вставить его в Eclipse. Причина одна - набрав это все своими руками ты намного большему научишься, нежели используя простой Copy/Paste.



Запускаем все наши требования-тесты. Зеленые! Ура!


Можно сохраняться...



В следующем уровне я хочу сделать так, чтобы пускало только одного пользователя, который заведомо известен системе. Все остальные пользователи (или если пароль неправильный) будут отправляться на страничку с уведомлением об не успешности процесса авторизации. Для этого мне понадобятся два новых требования...


Которые естественно пока не проходят...


Мы немного перепишем контроллер (зеленым для удобства восприятия подчеркнут тот код, который не менялся, а красным - измененный код)...


Так же нам понадобится новая jsp-страничка, которая будет информировать пользователя о неуспехе...


Что требования? Работают!


Я так же добавил те же самые требования, только в виде unit-тестов для LoginController...


Понадобился маленький фикс, чтобы они так же были зеленые...



Следующий прием использую для того, чтобы ответить на вопрос параноика "а действительно ли unit-требования требуют от кода то, что я хотел". Я просто комментирую строку за строкой и смотрю как unit-тесты перестают работать.




В конце концов я добиваюсь "зеленой полосы"...


Маленькое переименовывание тестов - чтобы было информативнее...


Сделаем еще один маленький класс, который будет запускать нам сервер...


Этот код должен быть уже знаком...


Запустим как консольное java-приложение...


И на всякий проверим ручками все ли ок. Да я параноик! Раньше, когда я не писал автоматических требований я знал что ошибаюсь, но не знал насколько часто это происходит. В среднем я ошибаюсь в своих суждениях 200 раз в день. А ты? Подсчитай и будешь приятно удивлен.

Так вот, если мы введем не то на login страничке...


То увидим уведомление с линком обратно на login'ку...


Кликая на который...



Оу! А вот это нововведение я придумал спонтанно и не написал на него теста-требования... Ошибочка получилась. Ничего исправим - важно другое, как в дальнейшем от таких ошибок обезопасить себя?

Итак требование и оно сразу работает!


Не верю в то, что оно проверяет именно то, что надо, а потому уберу на время проверяемую функциональность и проверю что требование сломалось с соответствующим сообщением...


И снова починю :)...


Вот теперь я спокоен и могу сохраняться!



Следующий уровень - я хочу из контроллера LoginController выделить новый модуль, который будет специализироваться на пользователях. Это подготовительный этап, который позволит в дальнейшем работать с модулями (login и user) независимо.

Начну с изменения требования - так, средствами IDE можно сделать желаемые изменения в коде намного быстрее...


Вот первый пример. Многие ошибки компиляции IDE Eclipse позволяет исправлять автоматически (добавлением соответствующего кода). Как то в нашем случае...


Интерфейсы - это основа модульности. Интерфейс - это список правил, описывающих то, как два модуля между собой взаимодействуют. Создаем новый интерфейс...


Ошибка компиляции сразу пропадет...


Решим подобным образом и другую ошибку...


Создали новый пустой конструктор, чем исправили ошибку компиляции...


Допишем то, что мы ходим видеть в этом конструкторе (ну для чего-то мы его создавали?). И проделывая финты с аутокомплиттером IDE допишем необходимый код...


Теперь делегируем логику проверки пользователя новому модулю, который мы только что передали через конструктор...


Снова дадим необходимый компилятору код с помощью IDE...


Еще ошибки, оказывается IDE не оставила старого конструктора...



Проэмулируем новый модуль в наших unit-требованиях...


То же проделаем и в других unit-требованиях...


А теперь займемся мытьем посуды. Каждый раз после еды (не я - жена моя) моет посуду. Это важный момент, иначе спустя неделю "не мытья" сложно будет найти чистую тарелку, а мытье (в пересчете на одну тарелку) отнимет намного больше времени, чем если бы умывальник был бы пуст.

Во всех требованиях дублируются одни и те хе строки...


Выделим их используя специальную конструкцию jUnit фреймворка аннотацию @Before. Метод, аннотированный таким образом, будет выполняться перед выполнением каждого требования. А это нами надо...


Так же дублируются и эти строчки...


Воспользуемся автоматизированной посудомоечной машиной IDE...


Укажем название метода, в который перенесутся эти строки...


И порадуемся чистоте раковины.


Если уйти от терминологии домохозяйки к терминологии программиста, то процесс мыться посуды (автоматизированны или ручной) называется Рефакторингом - изменение структуры кода без изменения его функциональной значимости. Кстати про функциональность - поменяли ли мы что-то или нет скажут нам наши автоматизированные требования-тесты. Чик...


...и готово. Все в этом модуле работает как мы того требуем. Или не работает? А что если мы внесли ошибку проводя рефакторинг тестов? Вполне возможно, а потому я еще раз все проглянул и успокоился.


А если запустить все тесты-требования нашего проекта, то увидим что не все работает. Причем видно что именно не работает. Ответ я этот получаю за пару секунд, а теперь представь, что тебе придется это все в браузере проверять руками? Мне лень, потому я пишу тесты-требования...

Решение мне видится таким...


Новый класс, который реализует функции нового модуля для работы с пользователями...


Запускаем тесты-требования...


Добавляем требуемую тестами проверку...


...Чик! И все работает. Повторюсь, "чик" этот занимает секунды, а если ты запомнишь горячую клавишу Alt-Shift-X,T...

Я снова хочу помыть посуду. Образовались два новых файла отвечающие за новый модуль, которые я хочу перенести в отдельный пакет для наглядности.

Снова попросим IDE сделать это...


Надеюсь понятно :)


А вот и результат...


Чиик?


Готово!

Хочу протестировать (т.е. написать для него требования) новый модуль независимо от остальных.

Создам новый класс...




Работают! Ладно, заморачиваться не буду. Верю!

Дальше моем посуду - избавляемся от дублирования.



Чиик!?


Работает!

Хух, сохраняемся!



Пока все :) Следующий этап - мы научим наш новый модуль работать с файлом, в котором будут перечислены все пользователи, имеющие возможность логиниться в систему. После авторизации пользователю будет предоставлен список экзаменов, которые он может пройти. В начале будет только один экзамен, состоящий из одного вопроса. В зависимости от выбранного варианта ответа экзамен будет считаться либо пройденным либо нет, о чем пользователю сообщат на последней страничке.

Продолжение следует...

19 комментариев:

  1. Так вот к чему было затишье то. :)

    ОтветитьУдалить
  2. Все делается исключительно под настроение, а потому результат есть, только тогда, когда он желанный.

    ОтветитьУдалить
  3. ух ты как круто, под настроение пройду по туториалам, туточки для меня много вкусненького

    ОтветитьУдалить
  4. Я вот также хочу в данный пост вгрызться с остервенением - не читал еще, но кажется вкусным! Спасибо Саша!

    ОтветитьУдалить
  5. Пиши еще.
    И пожелание, побольше текста, объясняющего что, как и зачем. Одними картинками сыт не будешь, как и наоборот )))

    ОтветитьУдалить
    Ответы
    1. Выложил все посты этой серии, что заппылились в черновиках. И хоть есть еще куда писать приложение, изначальная причина его написания сдулась, а значит это направление уже не приоритет. Но если очень надо - могу продолжить.

      Удалить
  6. Хороший мануал, только с библиотеками jetty неразбериха, на сайте eclipse пишут, что на codehouse архив идет с либами для jsp, но либа из этого архива не подошла, как и либа из мавеновского репозитория, не знаю почему, бился с ошибкой JSP support not configured, пока не скачал архив Download jetty-starter-jars-2.3.0.14.zip (http://sourceforge.net/projects/sageplugins/files/Jetty%20Starter/2.3.0/plugin/jetty-starter-jars-2.3.0.14.zip/download)

    ОтветитьУдалить
    Ответы
    1. Спасибо за комментарий.
      Да я это уже понял. Только что час мигрировал проект на Idea+Maven и обновлял либы. Зеленая полоса есть, но пошаманить пришлось...

      Удалить
  7. спасибо за урок. но очень много неразберихи, внезапно появляются новые файлы, повсюду очепятки. Хоть один из твоих проектов работает? я бы тебя не нанял.

    ОтветитьУдалить
    Ответы
    1. Спасибо за комментарий.
      Если Вам дам svn репозиторий со всеми ревизиями - это поможет? Вы хотите разобраться или так, просто?

      Удалить
    2. Давай плиз !!!
      Я тут пытаюсь научится веб-приложение делать, но для первого моего творения твой мануал (при том, что он очень хорош и полезен) тяжеловат, но все бы ничего, если бы ты на забывал комментировать даже мелочи, и не ленился больше расписывать !!! Мне очень нужна помощь в учебе!!!

      Удалить
    3. Вот, пожалуйста http://bit.ly/Tf2Ksd TrotiseSVN реппозиторий со всеми ревизиями (Eclipse) и последняя ревизия, мигрированная уже на Idea.

      Кстати, одно предостережение. Сейчас уже есть всякие разные Spring MVC и тому подобные удобные фремфорки. Статья была написана пару лет назад, а потому могла чуть устареть. Что в ней не устарело - ТДД (он за это время не поменялся ни сколько), мой подход к декомпозиции проблем (может быть кому-то будет полезным), ну и базовые вещи в Web - они так же не имезнились.

      Единственное, что я протупил (и я это уже знаю) - там скорее всего сервлеты Statefull (имеющие поля, состояние), а они должны быть Stateless иначе в многопоточной среде будут ошибки. Все хранить стоит в сесии...

      Тренпнинговый-проект для которого писался этот код так и не увидел свет, а потому чтобы не потерять наработы я выложил их в блог. Когда дойдеште до конца, напишите, найду время закончу его, тем более, что кажется мне сейчас этот код может пригодится в другом проекте...

      Успехов! Если что вот мой скайп - alexander.baglay

      Удалить
    4. Этот комментарий был удален автором.

      Удалить
  8. Давай репозиторий !!! А то проблем выше криши с твоим чудо-приложением !!!

    ОтветитьУдалить
  9. А можете написать пару статей для новчиков с использованием Spring

    И сборщиком maven

    ОтветитьУдалить
  10. Рекомендация для тех, кто проходит туториал:
    необходимо качать ту версию Jetty которая совместима с JWebUnit.
    Чтобы узнать какая совместима:
    смотрим в JWebUnit/lib какая версия в нём файликов jetty-*.jar
    (к примеру у меня jetty-http-8.1.7.jar).
    Поэтому качаем jetty 8! (если скачать jetty 9 - у меня ошибки виртуальной машины).

    ОтветитьУдалить
  11. Спасибо за статью! По JavaDoc гораздо дольше разбираться!
    И в книгах как написать Web-приложение материал есть, с тестированием все сложнее...

    ОтветитьУдалить