Привет. Вот ссылка на прошлую серию. Сейчас мы будем упрощать немного усложняя. Я хочу избавиться от лишних конcтрукторов конфигурирующих другие классы, но в то же время использующися только в тестах. Фу! Spring прикручивать пока очень громоздко, а потому надо написать свой класс, цель которого с помощью reflection вставлять в поля одного объекта ссылки на другие.
Для начала просто удалим то, что нам мешает. С этого почти всегда начинается рефакторинг. Воняет код? Удали его, и подумай, как сделать то же но иначе. Наш экзаменатор конфигурируется сервисом для работы с пользователями. Сделали мы это раньше для того, чтобы в тестах иметь возможность подсунуть mock. Удаляем и сразу видим все места, в которых этот конструктор использовался - его подсветит компилятор.
Дальше изменим код так, будь-то бы все необходимые утилиты уже есть, а имы ими просто пользуемся. Я назвал метод inject и передал в него два аргумента - то, куда вставляем, и то, что вставляем.
Вслед за этим последовала реализация, достаточная для озеленения тестов.
Я захотел сделать метод более информативным, а потому добавил локальную переменную injected, и если в результате вызова метода инъекции ничего не вставилось - я об этом сообщаю. Так удобнее, потому что иначе в случае поломки теста я не знаю, какова причина поломки - или я неправильно инъекцию провел или проблема все же в коде. А тут сразу все оборвется.
Кода в методе inject уже много, и он выполняет роль отличную от роли теста экзаменатора, а потому создадим для него новый класс. Так как это утилитка, то пакет выберем соответствующий. А назовем класс гордо Injector.
Копипастой (или через меню Refactoring) перемести метод в новый класс. Параллельно с этим я, как можно заметить, немного порефакторил метод и добавил в него новое поведение, а именно:
- метод не прокидывает внутренние Exception за свои пределы;
- метод не вставляет ничего в поля с типом Object - ибо туда можно вставить все, что угодно, а это нарушит задумку.
- метод возвращает на выходе тот же объект, что получил на вход удобства ради
- метод типизированный, чтобы предыдущее изменение принесло пользу а не вред - если был бы Object inject (Objct object..., то при пришлось бы в клиенте пользоваться Class Cast чтобы привести результат к исходному типу, а это, согласись, неудобно;
- метод переименован в injectAll, что больше соответствует алгоритму его работы.
Естественно для нового кода нужен новый тест.
Первое и самое просто - это проверка что инъекция состоится, если мы вставляем объект типа А в поле типа А
Еще один тест - проверяем, что объект типа А вставится во все поля типа А
Проверяем, что объект класса А реализующий интерфейс Б вставится в поле типа А.
Проверяем, что объект класса А реализующий интерфейс Б вставится в поле типа Б.
Проверяем, что объект класса А наследующий класс Б вставится в поле типа Б, а так же что объект класса Б вставится в поле типа Б при той же иерархии наследования.
Проверка, что при инъекции не производится вставка в поле типа Object.
Проверяем, что возникает сообщение о не вставке, если присутствует только Object поле.
Проверяем, что возникает сообщение о не вставке, если отсутствуют поля вообще.
Теперь можно спокойно упростить и контроллер логинки подобным образом, удаляя лишние конструкторы. А так же упростить тест для контроллера логинки в месте инициализации моков.
То же делаем для другого контроллера
И для для реализации экзаменатора
Все тесты?
Зеленые - значит коммитим.
Дальше мы займемся пошаговой реализацией многовопросного экзамена. Для начала нам надо разобраться в работе контроллера. Если сейчас включен режим вопроса, томы его готовим и выводим форму с вариантами ответа. Отправка формы включает режим результата в контроллере, а тот в свою очередь, обрабатывает его и готовит страничку результата.
Как минимум тут нам надо внести изменение - после первого вопроса должен быть второй, потом третий и так далее. Это уже контроллер должен решать когда хватит.
А потому мы переместим строчку с обработкой вопроса из обработчика режима result в обработчик режима question. Так же в этом обработчике выделим в локальную переменную параметра риквеста answer - в будущем мы будем проводить его анализ.
А анализ такой, что если нет никакого answer в request, то мы не будем делать ничего, что раньше делали в processQuestion. Это делается для того, чтобы избежать обработки несуществующего вопроса, который еще не готов до момента отображения первого вопроса.
Теперь нам немного стоит пошаманить в методе doQuestion. Идея в том, чтобы мы "тянули билет" у экзаменатора всего лишь раз, а последующие разы пользовались тем же билетом, нами же сохраненным в сессии.
Так как этот блок я отделил отступами сверху и снизу - верный признак - его можно выделить и дать ему имя. Это метод получения экзамена из сессии (конечно изначально метод не берет экзамен из сессии, а пользуется экзаменатором для этих целей, а в сессию только сохраняет, но ничего - пока остановимся на этом)
Теперь смело можно избавиться от бессмысленной локальной переменной.
Так же я предлагаю выделить из метода doQuestion строчку полечения экзамена, потому как мне показалось что ей там не место. Причина - контроллер со своим doPost имеет больше прав работать с сессией, и экзаменатором, чем doQuestion, который после перемещения станет писать в request данные о вопросе из экзамена - его специализация сузилась, а значит он стал более ценным. А контроллер пускай себе контролирует.
Простым переносом все не обошлось, а потому добавим еще один аргумент к методу doQestion, и передадим недостающий экзамен через него внутрь метода.
Теперь стоит продумать момент, когда вопросы перевелись и стоит уже показать страничку с результатами. Для этого напишем несуществующий метод hasMoreQuestions
и создадим его во всех интерфейсах и реализациях. Реализация пока что будет возвращать false - делается это потому, что я не хочу ускорить коммит, а значит я пока заставлю многовопросную логику работать с одним вопросом - так мне не прийдется править множества функциональных тестов, завязанных на исходное поведение - страница списка -> страница с вопросом -> страница с результатами.
Пора запускать все тесты. Первым делом меня заинтересовал поломанный функциональный тест. Я ожидал, что они все будут и дальше работать. В чем дело?
Оказывается в реализации метода hasMoreQuestions надо было продумать, что при первом вызове он должен вернуть true, а при последующих false. Так как билет (ExamQuestionAnswer) свое состояние хранит, то я попрошу подсчитать количество вызовов и в зависимости от его значения буду возвращать либо true либо false.
Тесты озеленили - идем дальше. То, что тесты контроллера поломаются я и не сомневался.
Итак тест на первый вопрос. Для начала у нас идет проверка, и нам надо запрограммировать что других вопросов в риквесте нет (т.е. никто еще не отвечал). Создадим метод и его реализацию.
Следующих несколько строчек я так же посчитал логичным выделить, потому как придумал для них нормальное название, а именно, тут программируются моки на случай, что экзамен не найден в сессии и как результат берется у экзаменатора.
Новое поведение проверки есть ли еще вопросы так же выделим в более информативный метод
Итого тест позеленел!
Остается еще один, а именно, когда вопросов больше нет и пользователь видит страничку результатов.
Легким движением руки....
Теперь очевидно нам больше ненужна ветка else if для режима result.
а метод processQuestion я решил переименовать в processAnswer
В тесте я увидел дублирвоание, но устранять я его собрался не в тесте а в тестируемом методе - дело в том, что это unit тест, тестирующий белый ящик, а значит дублирование в нем указывают на дублирование в в этом самом ящике. Устраним дублированеи у тестируемом методе, удалится оно и в тесте.
Для начала я перемещу еще выше строчку с получением экзамена, и передам его во все места, в которых требуется наличие билета. метод processAnswer раньше сам получал экзамен из сессии, а теперь ему этого делать не надо. То же касается метода doResult.
А теперь можно удалить дублирование и в тесте. Результат - все зеленое.
Еще я заменил два вызова одним, просто переместив его туда, где ему место
Снова блок, выделенный пустыми строками, которому я могу дать информативное имя - выделяю
Новый тест для случая прохождения второго вопроса. В нем есть блок, который можно и нужно выделить - выделяем.
В результате тест стал очень даже читабельный с точки зрения пользовательских историй. Посуди сам: для режима question, это первый вопрос, при этом экзамена нет в сесии, есть еще вопросы, готовим форму с вопросом, идем на jsp. Или: для режима question, сохраняем ответ, еще есть вопросы, готоим их к отображению, отображает на jsp. Как по мне очень ничего.
Еще один блок - выделяем.
Все тесты зелененькие!
Пора бы уже закоммититься, больше идей по улучшению у меня нет.
Начинаем следующую итерацию после перерыва с чего? Правильно - с запуска всех тестов.
Где хранится наш вопрос? Он размазан по get-ерам класса, его инкапсулирубщего .
Создадим из него копию
И добавим суффикс multiple
Но перед тем, как мы возьмемся за реализацию нового класса, я обратил внимание на то, что некоторые методы его интерфейса отвечают за получения информации о результате прохождения экзамена, а другая часть связана с процессом прохождения.
Эти пару методов, я выделил в отдельный интерфейс.
IDE предложила занаследоваться, но я тут предпочитаю аггрегаию, потому что список действий (или стратегия поведения класса) ExamQuestionAnswer не является ExamResult, но может его включать.
Заменим во всех местах, на которые укажет нам компилятор. Тут добавим:
Тут реализуем новый метод, а старые удалим
Добавим новое поле. Смыслв том, что теперь в экзамене есть информация о его результате в инкапсулированном виде.
Реализации результата нет - создаем.
Теперь дело за тестами. Моки надо немного перепрограммировать
Юнит тесты заработали. Пошли дальше.
Пускай имя пользователя хранится в результатах экзамена, ведь сам экзамен должен заботиться только о том, чтобы задавались вопросы и записывались результаты, но не хранением информации о том,кто его проходит.
Так же надо добавить возможность добавлять ответ за ответом в результаты.
Новый метод интерфейса результаты
Еще один метод в реализации
Попробуем реализовать результаты в первом приближении
Но тесты не проходят
исправим
Сейчас большой рывочек - я реализую класс, который мы создавали в самом начале - многовопросный экзамен.
Выглядит неплохо. Подключим его взамен старой, одновопросной, реализации.
Первые глюки. Ну и неудивительно - я другого и не ожидал. Столько кода написал за раз - конечно там купа багов!
Почему-то выводится сразу второй вопрос, хотя ожидаю первый. Но с нумерацией все ок.
Небольшой фикс в начальном индексе (в программировании индексы с 0 начинаются, блин) не особо помог, теперь непорядки с нумерацией вопросов.
Попробуем решить иначе и получаем еще один зеленый тест.
Теперь проблемс с тем, что раньше у нас после одного вопроса сразу была страничка результатов, а теперь у нас там второй вопрос.
Проверим эту гипотезу и заремарим два других вопроса в многовопросном экзамене, но так же добавим одну строчку, которую я забыл добавить раньше
Все супер!
Теперь я хочу изменить функциональные тесты так, чтобы они учитывали три вопроса а не один.
естественно они не сработают, а потому разремарим два новых вопроса в многовопросном экзамене
Уже лучше, но все же немного не то
А все дело в том, что надо поменять старую логику проверки одного вопроса экзамена на проверку трех
А это ведет к расширению интерфейса результата экзамена новым методом getAnswers
Старый при этом можно удалить
Наведем порядок в реализации
Я решил, что Integer[] будет лучше, чем int[] потому, чтоне хочу тратить много времени на поиск ответа, как настраивать мок с помощью EasyMock если у нас массив примитивов. Фу, но так быстрее. Потом исправим. Рефакторинг я люблю, а потому без угрызения совести оставляю подобные кизячки на потом. Кизячком я это считаю потому, что из за тестов мне пришлось менять код.
Подправим еще и unit тесты
И экзаменатора в связи с изменением интерфейсной части
Все тесты?
Зеленые! Коммитимся, я все сделал, что хотел.
Следующая моя задумка в том, что пользователь не может возвращаться назад и отвечать на вопрос после того, как он сдал экзамен. Вероятно пользователю захочется это сделать, если на страничке результатов он вдруг заметит, что провалил экзамен - в таком случае его стоит направить на логинку.
Замечу, что после коммита я тестов всех не запускал только потому, что я не делал перерыва.
Одна простая валидация на то, залогинен пользователь или нет и вылогинивание пользователя, если он на страничке result и тест прошел
Тэкс, дисграмка. Значит смотрим на тест - он провален, после смотрим с каким сообщением - ожидаемый текст не найден на страничке, дальше смотрим где это произошло в тесте, и потом уже что там в реале было.
Немного подумал и понял, что надо так же из сессии вычищать не только user name но и его старый экзамен, иначе второй пользователь, если попытается пройти экзамен - будет работать со старым экзаменом, а там и отвечать-то нечего - как результат сразу страничка с результатами :)
Раз эти две строчки спаренные, то я их выделю в метод logoutUser.
Еще один Extract method для проверки если юзер залогинен
Конечно же поломаются unit тесты - они всегда ломаются, когда меняются внутренности тестируемых ими методов - фишка тестирования белого ящика.
Программируем новое поведение
А вот с этим тестом не так все просто.
Вроде и logout сделал, как положено, но все же где-то ошибся.
Выделю ка я в локальную переменную получение сессии
И все заработало!
Все тесты так же зеленые
Я добавлю еще один unit тест проверяющий, что незалогиненный пользователь не может ничего делать с контроллером.
И еще один функциональный тест, который говорит что если юзер не выбирет варианты ответа и попробует так отвтеить, то ему предложит тот же вопрос
Так же то, что юзерпару раз отправлял форму без ответов никак не скажется на остальных вопросах - как только он наконец-то ответит - все пойдет дальше по плану.
Теперь повыделяем немного - я хочу устранить все дублирование, которое накопилось в этом функциональном тесте
Тут выделениев три этапа - вначале локализируем переменную, потом выделяем метод, а после встраиваем временную локальную переменну. Так делаем во всех подобных местах. Такой подходи я использую тогда, когда в нескольких местах встречается дублирование с отличием в одной или нескольких константах.
Ну и дальше просто экстрактим...
Подправил название
Вот как выглядит результат
А главное все тесты проходят, а потому коммичусь
На сегодня все. Не переключайтесь...
Для начала просто удалим то, что нам мешает. С этого почти всегда начинается рефакторинг. Воняет код? Удали его, и подумай, как сделать то же но иначе. Наш экзаменатор конфигурируется сервисом для работы с пользователями. Сделали мы это раньше для того, чтобы в тестах иметь возможность подсунуть mock. Удаляем и сразу видим все места, в которых этот конструктор использовался - его подсветит компилятор.
Дальше изменим код так, будь-то бы все необходимые утилиты уже есть, а имы ими просто пользуемся. Я назвал метод inject и передал в него два аргумента - то, куда вставляем, и то, что вставляем.
Вслед за этим последовала реализация, достаточная для озеленения тестов.
Я захотел сделать метод более информативным, а потому добавил локальную переменную injected, и если в результате вызова метода инъекции ничего не вставилось - я об этом сообщаю. Так удобнее, потому что иначе в случае поломки теста я не знаю, какова причина поломки - или я неправильно инъекцию провел или проблема все же в коде. А тут сразу все оборвется.
Кода в методе inject уже много, и он выполняет роль отличную от роли теста экзаменатора, а потому создадим для него новый класс. Так как это утилитка, то пакет выберем соответствующий. А назовем класс гордо Injector.
Копипастой (или через меню Refactoring) перемести метод в новый класс. Параллельно с этим я, как можно заметить, немного порефакторил метод и добавил в него новое поведение, а именно:
- метод не прокидывает внутренние Exception за свои пределы;
- метод не вставляет ничего в поля с типом Object - ибо туда можно вставить все, что угодно, а это нарушит задумку.
- метод возвращает на выходе тот же объект, что получил на вход удобства ради
- метод типизированный, чтобы предыдущее изменение принесло пользу а не вред - если был бы Object inject (Objct object..., то при пришлось бы в клиенте пользоваться Class Cast чтобы привести результат к исходному типу, а это, согласись, неудобно;
- метод переименован в injectAll, что больше соответствует алгоритму его работы.
Естественно для нового кода нужен новый тест.
Первое и самое просто - это проверка что инъекция состоится, если мы вставляем объект типа А в поле типа А
Еще один тест - проверяем, что объект типа А вставится во все поля типа А
Проверяем, что объект класса А реализующий интерфейс Б вставится в поле типа А.
Проверяем, что объект класса А реализующий интерфейс Б вставится в поле типа Б.
Проверяем, что объект класса А наследующий класс Б вставится в поле типа Б, а так же что объект класса Б вставится в поле типа Б при той же иерархии наследования.
Проверка, что при инъекции не производится вставка в поле типа Object.
Проверяем, что возникает сообщение о не вставке, если присутствует только Object поле.
Проверяем, что возникает сообщение о не вставке, если отсутствуют поля вообще.
Теперь можно спокойно упростить и контроллер логинки подобным образом, удаляя лишние конструкторы. А так же упростить тест для контроллера логинки в месте инициализации моков.
То же делаем для другого контроллера
И для для реализации экзаменатора
Все тесты?
Зеленые - значит коммитим.
Дальше мы займемся пошаговой реализацией многовопросного экзамена. Для начала нам надо разобраться в работе контроллера. Если сейчас включен режим вопроса, томы его готовим и выводим форму с вариантами ответа. Отправка формы включает режим результата в контроллере, а тот в свою очередь, обрабатывает его и готовит страничку результата.
Как минимум тут нам надо внести изменение - после первого вопроса должен быть второй, потом третий и так далее. Это уже контроллер должен решать когда хватит.
А потому мы переместим строчку с обработкой вопроса из обработчика режима result в обработчик режима question. Так же в этом обработчике выделим в локальную переменную параметра риквеста answer - в будущем мы будем проводить его анализ.
А анализ такой, что если нет никакого answer в request, то мы не будем делать ничего, что раньше делали в processQuestion. Это делается для того, чтобы избежать обработки несуществующего вопроса, который еще не готов до момента отображения первого вопроса.
Теперь нам немного стоит пошаманить в методе doQuestion. Идея в том, чтобы мы "тянули билет" у экзаменатора всего лишь раз, а последующие разы пользовались тем же билетом, нами же сохраненным в сессии.
Так как этот блок я отделил отступами сверху и снизу - верный признак - его можно выделить и дать ему имя. Это метод получения экзамена из сессии (конечно изначально метод не берет экзамен из сессии, а пользуется экзаменатором для этих целей, а в сессию только сохраняет, но ничего - пока остановимся на этом)
Теперь смело можно избавиться от бессмысленной локальной переменной.
Так же я предлагаю выделить из метода doQuestion строчку полечения экзамена, потому как мне показалось что ей там не место. Причина - контроллер со своим doPost имеет больше прав работать с сессией, и экзаменатором, чем doQuestion, который после перемещения станет писать в request данные о вопросе из экзамена - его специализация сузилась, а значит он стал более ценным. А контроллер пускай себе контролирует.
Простым переносом все не обошлось, а потому добавим еще один аргумент к методу doQestion, и передадим недостающий экзамен через него внутрь метода.
Теперь стоит продумать момент, когда вопросы перевелись и стоит уже показать страничку с результатами. Для этого напишем несуществующий метод hasMoreQuestions
и создадим его во всех интерфейсах и реализациях. Реализация пока что будет возвращать false - делается это потому, что я не хочу ускорить коммит, а значит я пока заставлю многовопросную логику работать с одним вопросом - так мне не прийдется править множества функциональных тестов, завязанных на исходное поведение - страница списка -> страница с вопросом -> страница с результатами.
Пора запускать все тесты. Первым делом меня заинтересовал поломанный функциональный тест. Я ожидал, что они все будут и дальше работать. В чем дело?
Оказывается в реализации метода hasMoreQuestions надо было продумать, что при первом вызове он должен вернуть true, а при последующих false. Так как билет (ExamQuestionAnswer) свое состояние хранит, то я попрошу подсчитать количество вызовов и в зависимости от его значения буду возвращать либо true либо false.
Тесты озеленили - идем дальше. То, что тесты контроллера поломаются я и не сомневался.
Итак тест на первый вопрос. Для начала у нас идет проверка, и нам надо запрограммировать что других вопросов в риквесте нет (т.е. никто еще не отвечал). Создадим метод и его реализацию.
Следующих несколько строчек я так же посчитал логичным выделить, потому как придумал для них нормальное название, а именно, тут программируются моки на случай, что экзамен не найден в сессии и как результат берется у экзаменатора.
Новое поведение проверки есть ли еще вопросы так же выделим в более информативный метод
Итого тест позеленел!
Остается еще один, а именно, когда вопросов больше нет и пользователь видит страничку результатов.
Легким движением руки....
Теперь очевидно нам больше ненужна ветка else if для режима result.
а метод processQuestion я решил переименовать в processAnswer
В тесте я увидел дублирвоание, но устранять я его собрался не в тесте а в тестируемом методе - дело в том, что это unit тест, тестирующий белый ящик, а значит дублирование в нем указывают на дублирование в в этом самом ящике. Устраним дублированеи у тестируемом методе, удалится оно и в тесте.
Для начала я перемещу еще выше строчку с получением экзамена, и передам его во все места, в которых требуется наличие билета. метод processAnswer раньше сам получал экзамен из сессии, а теперь ему этого делать не надо. То же касается метода doResult.
А теперь можно удалить дублирование и в тесте. Результат - все зеленое.
Еще я заменил два вызова одним, просто переместив его туда, где ему место
Снова блок, выделенный пустыми строками, которому я могу дать информативное имя - выделяю
Новый тест для случая прохождения второго вопроса. В нем есть блок, который можно и нужно выделить - выделяем.
В результате тест стал очень даже читабельный с точки зрения пользовательских историй. Посуди сам: для режима question, это первый вопрос, при этом экзамена нет в сесии, есть еще вопросы, готовим форму с вопросом, идем на jsp. Или: для режима question, сохраняем ответ, еще есть вопросы, готоим их к отображению, отображает на jsp. Как по мне очень ничего.
Еще один блок - выделяем.
Все тесты зелененькие!
Пора бы уже закоммититься, больше идей по улучшению у меня нет.
Начинаем следующую итерацию после перерыва с чего? Правильно - с запуска всех тестов.
Где хранится наш вопрос? Он размазан по get-ерам класса, его инкапсулирубщего .
Создадим из него копию
И добавим суффикс multiple
Но перед тем, как мы возьмемся за реализацию нового класса, я обратил внимание на то, что некоторые методы его интерфейса отвечают за получения информации о результате прохождения экзамена, а другая часть связана с процессом прохождения.
Эти пару методов, я выделил в отдельный интерфейс.
IDE предложила занаследоваться, но я тут предпочитаю аггрегаию, потому что список действий (или стратегия поведения класса) ExamQuestionAnswer не является ExamResult, но может его включать.
Заменим во всех местах, на которые укажет нам компилятор. Тут добавим:
Тут реализуем новый метод, а старые удалим
Добавим новое поле. Смыслв том, что теперь в экзамене есть информация о его результате в инкапсулированном виде.
Реализации результата нет - создаем.
Теперь дело за тестами. Моки надо немного перепрограммировать
Юнит тесты заработали. Пошли дальше.
Пускай имя пользователя хранится в результатах экзамена, ведь сам экзамен должен заботиться только о том, чтобы задавались вопросы и записывались результаты, но не хранением информации о том,кто его проходит.
Так же надо добавить возможность добавлять ответ за ответом в результаты.
Новый метод интерфейса результаты
Еще один метод в реализации
Попробуем реализовать результаты в первом приближении
Но тесты не проходят
исправим
Сейчас большой рывочек - я реализую класс, который мы создавали в самом начале - многовопросный экзамен.
Выглядит неплохо. Подключим его взамен старой, одновопросной, реализации.
Первые глюки. Ну и неудивительно - я другого и не ожидал. Столько кода написал за раз - конечно там купа багов!
Почему-то выводится сразу второй вопрос, хотя ожидаю первый. Но с нумерацией все ок.
Небольшой фикс в начальном индексе (в программировании индексы с 0 начинаются, блин) не особо помог, теперь непорядки с нумерацией вопросов.
Попробуем решить иначе и получаем еще один зеленый тест.
Теперь проблемс с тем, что раньше у нас после одного вопроса сразу была страничка результатов, а теперь у нас там второй вопрос.
Проверим эту гипотезу и заремарим два других вопроса в многовопросном экзамене, но так же добавим одну строчку, которую я забыл добавить раньше
Все супер!
Теперь я хочу изменить функциональные тесты так, чтобы они учитывали три вопроса а не один.
естественно они не сработают, а потому разремарим два новых вопроса в многовопросном экзамене
Уже лучше, но все же немного не то
А все дело в том, что надо поменять старую логику проверки одного вопроса экзамена на проверку трех
А это ведет к расширению интерфейса результата экзамена новым методом getAnswers
Старый при этом можно удалить
Наведем порядок в реализации
Я решил, что Integer[] будет лучше, чем int[] потому, чтоне хочу тратить много времени на поиск ответа, как настраивать мок с помощью EasyMock если у нас массив примитивов. Фу, но так быстрее. Потом исправим. Рефакторинг я люблю, а потому без угрызения совести оставляю подобные кизячки на потом. Кизячком я это считаю потому, что из за тестов мне пришлось менять код.
Подправим еще и unit тесты
И экзаменатора в связи с изменением интерфейсной части
Все тесты?
Зеленые! Коммитимся, я все сделал, что хотел.
Следующая моя задумка в том, что пользователь не может возвращаться назад и отвечать на вопрос после того, как он сдал экзамен. Вероятно пользователю захочется это сделать, если на страничке результатов он вдруг заметит, что провалил экзамен - в таком случае его стоит направить на логинку.
Замечу, что после коммита я тестов всех не запускал только потому, что я не делал перерыва.
Одна простая валидация на то, залогинен пользователь или нет и вылогинивание пользователя, если он на страничке result и тест прошел
Тэкс, дисграмка. Значит смотрим на тест - он провален, после смотрим с каким сообщением - ожидаемый текст не найден на страничке, дальше смотрим где это произошло в тесте, и потом уже что там в реале было.
Немного подумал и понял, что надо так же из сессии вычищать не только user name но и его старый экзамен, иначе второй пользователь, если попытается пройти экзамен - будет работать со старым экзаменом, а там и отвечать-то нечего - как результат сразу страничка с результатами :)
Раз эти две строчки спаренные, то я их выделю в метод logoutUser.
Еще один Extract method для проверки если юзер залогинен
Конечно же поломаются unit тесты - они всегда ломаются, когда меняются внутренности тестируемых ими методов - фишка тестирования белого ящика.
Программируем новое поведение
А вот с этим тестом не так все просто.
Вроде и logout сделал, как положено, но все же где-то ошибся.
Выделю ка я в локальную переменную получение сессии
И все заработало!
Все тесты так же зеленые
Я добавлю еще один unit тест проверяющий, что незалогиненный пользователь не может ничего делать с контроллером.
И еще один функциональный тест, который говорит что если юзер не выбирет варианты ответа и попробует так отвтеить, то ему предложит тот же вопрос
Так же то, что юзерпару раз отправлял форму без ответов никак не скажется на остальных вопросах - как только он наконец-то ответит - все пойдет дальше по плану.
Теперь повыделяем немного - я хочу устранить все дублирование, которое накопилось в этом функциональном тесте
Тут выделениев три этапа - вначале локализируем переменную, потом выделяем метод, а после встраиваем временную локальную переменну. Так делаем во всех подобных местах. Такой подходи я использую тогда, когда в нескольких местах встречается дублирование с отличием в одной или нескольких константах.
Ну и дальше просто экстрактим...
Подправил название
Вот как выглядит результат
А главное все тесты проходят, а потому коммичусь
На сегодня все. Не переключайтесь...
Санёк, это было жестоко... Я палец смозолил, пока скроллил.
ОтветитьУдалитьСпасмбо за веселый коммент :)
ОтветитьУдалитьДа было дело. Писал такие монстры. Руку отмозолил, и быстренько получалось. Сейчас все больше видео снимаю - эффективнее оно, хотя если не с первого дубля, то озвучка и монтаж потом отнимает время соизмеримое с созданием скриншотов. Давно вообще это было. Я эти посты вчера достал из черновиков. По 2 года им. Время как летит...
Если не пользу учащемуся, то хоть троллей отпугивать будут - и то польза.