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


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

понедельник, 9 января 2012 г.

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

В первой части я расказал как настроить самое основное окружение. Во второй части мы начали делать login page и почти закончили ее.

Итак продолжим. Всякая работа начинается с запуска тестов-требований. так мы понимаем, что все работает как прежде и можем спокойно продолжать разработку...


Напомню, что я хочу сделать User сервис таким, чтобы он работать с простыми файлами. Для этого я напишу новую версию класса, а старую оставлю в покое.

Переименуем UserServiceTest с тем, чтобы дать понять, что он тестирует старую реализацию знающую всего лишь про одного пользователя - назовем ее Mock. Если интересно, читаем дальше...


Если стоя курсором на имени нажать комбинацию клавиш Alt-Shift-R то Eclipse предложит заменить имя. После нажатия Enter - все места, где встречается упоминание этого класса так же будут обновлены....


Теперь используем CopyPast подход для клонирования теста. Новый класс назовем UserServiceFileTest...


В сердце его будем использовать новую реализацию (которой еще пока нет) некоторого интерфейса (его тоже пока нет)....


Создадим их!...





Если теперь запустить тесты, то можно увидеть, что проходят только двое их трех...


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


Если мы заменим false на true - то увидим другую картину: тот тест, который работа поломается и наоборот. Это хорошо демонстрирует тот факт, что зеленый тест вовсе не значит что код работает...


Напишем реализацию основанную на Properties классе - у него есть набор методов, которые нам подходят...


Пишем код игнорируя ошибки а потом причесываем его...





Еще одна правочка от меня...


Теперь нам надо создать файл с описанием пользователей...


Имя должно соответствовать тому, которое мы указали ранее...


Добавляем нового пользователя...


Чик! И все зазеленело...


Я набрался смелости и воспользовался параметризацией тестов (фича jUnit4) с тем, чтобы тестировать для разных пользователей...


добавим теперь этих разных пользователей...


Чик! И тесты работают...


Научим тест создавать этот файл с необходимым набором пользоватльских данных...


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


Но вот неожиданная ошибка!...


Перепишем логику загрузки файла и все пройдет...


Еще раз все тесты...


Теперь можно браться за переключение системы на новую реализацию...


Часть тестов перестала работать...


Но мы добавим файл с пользователями...


И все пройдет!...

Напишем еще одно требование но для другого пользователя...


После добавление этого пользователя - тест стал зеленый...


и ничего более не поломалось...


Пока писал одно пришла идея как сделать другое - для удобства копирования...


Вот как это выглядит в браузере...


Сохраняемся!..



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


Мы можем сделать CopyPast инфраструктуры...


Но я все же предпочитаю другой вид повторного использования кода...Выделим (Extract) в новые методы все места, в которых используется поле server. Это метод startServer...




и stopServer...




Я вначале думал сделать еще один метод, для получения порта...




Но позже передумал и объединил два метода startServe и getServerPort вместе. После я перенес их в новый класс ServerRunner, который настроил как Singleton. В этот же  ServerRunner я добавил еще один метод join и некоторую примитивную проверку. На результат можно глянуть тут:


А вот как изменятся его клиенты...



А от оставшейся части дублирование (инициализация tester на основе server) я избавлюсь с помощью наследования... Мне почти ничего не надо делать в ручную - все сделает Eclipse...


Так будет называться родитель для всех тестов требований, которые работают со стороны клиента. Часто такие тесты требования называют функциональными, так как они тестируют все приложение целиком....


Вот и все :)...


А вот зачем мы все это делали...


Теперь можно написать новое требование...


Которое естественно не пройдет потому что нет еще того что требуется...


Старой страничке дадим новое имя в соответствии с новой ролью...


И заменим все ссылки по имени на нее....


Внимание! С заменой во всех местах стоит поосторожнее, а потому я сузил поиск выралв галочку CaseSencetive и просмотрел все изменения...




Вот, собственно, все места, в которых прошла замена ...



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


Легким движением руки...


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


Изменения простые..



И все работает....


Сохранимся!...



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


Естественно требование провалится...


Но мы добавим новый контроллер...




И теперь сообщение об ошибке немного другое...


Оказывается есть два способа общения между сервером (servlet) и клиентом (browser) - POST и GET. Мне стоило реализовать get обработчик...


И сообщенеи поменялось с ошибки (красная иконка возле имени - сообщает о возникновении Exception в тесте) на warning (синяя иконка - в целом тоже ошибка, но когда слетает один из assert'ов теста)...


Сделаем переход на страничку с вопросом...


Создадим эту страничку...


Допишем туда свой контент...


Тест прошел!..


Можно проверить в браузере...


Чтобы работала последняя форма - надо реализовать обработчик POST...


Создать соответствующую страничку...


Наполнить ее контентом...


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


Я проверю руками...

Вроде как работает.

Еще одно требование - когда выбрали другой вопрос на результирующей страничке надпись о провале экзамена....


Готово!...


Надо расширять контроллер, чтобы он принимал решение сдал или нет...


И результирующую страничку....


Тест работает!...


Сохранимся...



Следующим шагом я хочу перенести все прошитые в коде (jsp) константы в контроллер. Это можно называть рефакторингом, т.к. ничего функционально не поменяется - просто код станет качественнее...

Все же я поменяю требования, но только в мелочах. К примеру я хочу выделить Hello world! везде, так как это название экзамена и отдельная константа...


Немного поменяю заголовок...


Требования требуют новое, и система им не соответствует - это мы и наблюдаем с красной полосой...


Пошли исправляться...


Один работает...


Дальше фиксим...


Все чисто!...


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




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


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



Тест попрежнему остался рабочим, а значит этим шагом я ничего не поломал...

Исправим мок в соответствии с новыми правилами...


И его тест использующий его так же прошел (осталось всего 3 поломанных теста из 5)! Следующий шаг...



И осталось 2 из 5...


Еще усилие...


Все работает!


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


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


Если константы убрали со странички, то контроллер их должен устанавливать...


С JSTL все намного проще...


Чик и все зеленое!...


Так же я заменю все конструкции <%= bla bla %> на другие , говорят что первые конструкции грех использовать. Не верьте им - все сами проверяйте...


то же на другой страничке...


Проверка, может где еще есть "грешная" конструкция...


Еще хочу навести порядок в новом контроллере и выделить три метода. Do list...


Do result...


и Do Question...



Тесты как и прежде работают, значит рефакторинг удался...


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


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



Это все мне позволить сделать следующий рефакторинг... Согласись, так лучше чем раньше.


Теперь самое время написать unit-тесты для нашего свежевыпеченного контроллера, пока он больше меняться сильно не будет (или будет?)...

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

2 комментария:

  1. Очень познавательно, но что нужно что бы jstl заработал на jetty?
    Ранее были добавлены только библиотеки:
    jetty-6.1.25.jar, jetty-util-6.1.25.jar, servlet-api-2.5-20081211.jar.
    Или здесь уже другая jetty используется?

    ОтветитьУдалить
    Ответы
    1. Прочитайте пожалуйста этот комментарий там есть линк на скачивание исходного кода проекта.
      Он писался давно и сейчас все пошло чуть вперед. Например я больше либы не подключаю вручную, а пользуюсь maven.
      В любом случае гугление что-то вроде "jstl jar file" должно вывести на название jar файла, в котором хранится jstl реализация.

      Удалить