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


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

среда, 16 марта 2011 г.

Java for fun: Разрабатываем Fitnesse тесты #1

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

К чему (в частности) стремятся Agile проекты? Минимум документации в пользу самотестирующегося и самодокументирующегося кода. А все потому, что документацию надо поддерживать наряду с кодом. Итак, если есть тесты, которые тестируют систему, а еще они написаны понятным языком, то это хорошо. Но вместе с тем в больших проектах от документации не особо и уходят. Ну и неудивительно - документация описывает систему на достаточно абстрактном уровне (бизнеса), а тесты они все же сильно привязаны к интерфейсам модулей (понятным только программистам). Заказчику легче работать с тестами первого типа, да и вообще, с не с тестами, а с текстовыми документами, графиками, табличками. Код же для программистов. И возникает вопрос, как тут избавиться от поддержки двух версий описания системы (код и документ с таблицами)? АА выход придумали и называется он Fitnesse.

Рассмотрим жизненный цикл документа в команде, которая работает с заказчиком с помощью Fitnesse. Все начинается с того, что у заказчика загорается лампочка и он начинает описывать команде разработчиков, как должна работать будущая система. Вначале тексту дает, позже у программистов появляются наводящие вопросы и чаще всего графики, таблички, блок-схемы...

Давайте рассмотрим пример подобного требования на примере некого устройства.

Необходимо смоделировать устройство, позволяющее производить арифметические вычисления. Устройство состоит из кнопок «+» «–» «=», цифровой клавиатуры с кнопкой «.» и кнопки «с» (сброс). Нажатие на каждую кнопку считается операцией.

Сброс состояния устройства
 
Операция
Показания экрана после выполнения операции
C
0
1
1
C
0

Ввод числа в устройство

Операция
Показания экрана после выполнения операции
C
0
1
1
2
12
5
125
7
1257
8
12579

Ввод дробного числа

Операция
Показания экрана после выполнения операции
C
0
5
5
6
56
7
567
.
567.
1
567.1
2
567.12

Далее, в колонке «Операция» (для удобства) допустимо упоминание нескольких операций идущих подряд, например.

Операция
Показания экрана после выполнения операции
C123.567
123.567

что равносильно

Операция
Показания экрана после выполнения операции
C
0
1
1
2
12
3
123
.
123.
5
123.5
6
123.56
7
123.567

Операция суммирования
Суммирование - это операция, которая работает так

Операнд 1
Операнд 2
Результат
123
456
579
45
1
46
0
1
1
10
1200
1210
555
777
1132
 
Суммирование двух чисел

Операция
Показания экрана после выполнения операции
C123
123
+
123
456
456
=
579

Суммирование более двух чисел

Операция
Показания экрана после выполнения операции
C123
123
+
123
456
456
+
579
567
567
+
1146
111
111
=
1257

Операция вычитание
 Вычитание - это операция, которая работает так

Операнд 1
Операнд 2
Результат
222
111
111
333
3
330
0
1
-1
23
25
-2
57
57
0

Вычитание двух и более чисел работает аналогично суммированию

Операция
Показания экрана после выполнения операции
C123
123
-
123
456
456
=
-333


Операция
Показания экрана после выполнения операции
C123
123
123
456
456
-333
567
567
-900
111
111
=
-1011

Можно комбинировать суммирование и вычитание

Операция
Показания экрана после выполнения операции
C123
123
­–
123
456
456
+
-333
567
567
=
234

Отрицательное число в качестве первого операнда

Операция
Показания экрана после выполнения операции
C
0
­–123
123
+
-123
124
124
=
1
Вот такой такой вот документ может случиться в результате общения команды разработчиков с заказчиком.

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

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



Архив распакуем в какую-то папку в корне (в принципе не важно) диска и осмотримся внутри


Нас интересует файл run.bat запускающий сервер. Создадим рядом еще один bat файл с таким содержимым.

run -p 8085

В результате запуска этого файла мы увидим консоль с текстом

D:\fitnesse>run -p 8085

D:\fitnesse>java -Xmx100M -jar fitnesse.jar -p 8085
FitNesse (v20090321) Started...
        port:              8085
        root page:         fitnesse.wiki.FileSystemPage at ./FitNesseRoot
        logger:            none
        authenticator:     fitnesse.authentication.PromiscuousAuthenticator
        html page factory: fitnesse.html.HtmlPageFactory
        page version expiration set to 14 days.

Это значит - все прошло успешно и мы можем запустить в своем браузере localhost:8085


По этому сайту можно походить, а когда надоест то в адресной строке браузера введи http://localhost:8085/CalculatorTest
Т.к. это википодобная система, то тебе предложат создать новую страничку, внутренность которой наполни содержимым документа заказчика. Строчку "!contents -R2 -g -p -f -h" моно удалить. Напомню так же, что таблицы в wiki коде строятся с помощью символа "|".


Вот код из документа, конвенртированный в wiki формат.

Необходимо смоделировать устройство, позволяющее производить арифметические вычисления. Устройство состоит из кнопок «+» «–» «=», цифровой клавиатуры с кнопкой «.» и кнопки «с» (сброс). Нажатие на каждую кнопку считается операцией. 

Сброс состояния устройства

|Операция|Показания экрана после выполнения операции|
|C|0|
|1|1|
|C|0|

Ввод числа в устройство

|Операция|Показания экрана после выполнения операции|
|C|0|
|1|1|
|2|12|
|5|125|
|7|1257|
|8|12579|

Ввод дробного числа

|Операция|Показания экрана после выполнения операции|
|C|0|
|5|5|
|6|56|
|7|567|
|.|567.|
|1|567.1|
|2|567.12|

Далее, в колонке «Операция» (для удобства) допустимо упоминание нескольких операций идущих подряд, например.

|Операция|Показания экрана после выполнения операции|
|с123.567|123.567|

что равносильно

|Операция|Показания экрана после выполнения операции|
|C|0|
|1|1|
|2|12|
|3|123|
|.|123.|
|5|123.5|
|6|123.56|
|7|123.567|

Операция суммирования
Суммирование - это операция, которая работает так

|Операнд 1|Операнд 2|Результат|
|123|456|579|
|45|1|46|
|0|1|1|
|10|1200|1210|
|555|777|1132|

Суммирование двух чисел

|Операция|Показания экрана после выполнения операции|
|C123|123|
|+|123|
|456|456|
|=|579|

Суммирование более двух чисел

|Операция|Показания экрана после выполнения операции|
|C123|123|
|+|123|
|456|456|
|+|579|
|567|567|
|+|1146|
|111|111|
|=|1257|

Операция вычитание
Вычитание - это операция, которая работает так

|Операнд 1|Операнд 2|Результат|
|222|111|111|
|333|3|330|
|0|1|-1|
|23|25|-2|
|57|57|0|

Вычитание двух и более чисел 
Работает аналогично суммированию

|Операция|Показания экрана после выполнения операции|
|C123|123|
|-|123|
|456|456|
|=|-333|

|Операция|Показания экрана после выполнения операции|
|C123|123|
|–|123|
|456|456|
|–|-333|
|567|567|
|–|-900|
|111|111|
|=|-1011|

Комбинирование операций

|Операция|Показания экрана после выполнения операции|
|C123|123|
|–|123|
|456|456|
|+|-333|
|567|567|
|=|234|

Отрицательное число в качестве первого операнда

|Операция|Показания экрана после выполнения операции|
|C|0|
|–123|123|
|+|-123|
|124|124|
|=|1|

А вот как выглядит это все после нажатия кнопки Save. Впрочем, ничего необычного - это делает wiki.


Документ как документ, но нажмем кнопку Test на панели слева.


Что случилось? А случилось вот что - мы попробовали запустить тест, описанный в этом документе. Как такое возможно? А просто - каждая табличка представляет собой проверку и называется fixture (фикстура). Как мы определили раньше - в нашем документе-тесте два вида таблиц-фикстур. Пока фикстуры наши не совсем рабочие, о чем свидетельствует окраска таблиц в желтый цвет с вклиненным сообщением "Could not find fixture: Операция." Чтобы таблички-фикстуры заработали - надо описать java класс, обрабатывающий данные описанные в табличке-фикстуре документа-теста. Тут как раз и начинается третий этап обработки требований заказчика командой, работающей с использованием Fitnesse.

Создадим новый java проект в вашей любимой IDE (я юзаю Eclipse). К этому времени у тебя должна стоять виртуальная машина java и эта самая IDE.


Приличия ради я создам пакет "calculator.fixtures", в котором будут размещены все обе фикстуры.


Далее создадим папочку lib и скопируем туда библиотеку fitnesse.jar из папки с установленным fitnesse.


Эту библиотеку добавим к проекту.


Дальше создадим таких два класса

package calculator.fixtures;

import fit.Fixture;

public class CalculatorActionFixture extends Fixture {
 
}

package calculator.fixtures;

import fit.Fixture;

public class CalculatorOperationFixture extends Fixture {
 
}

Вернемся к шанему документу-тесту, нажмем кнопку "Edit" и вставим первой строчкой путь к папке с компилированными java классами проекта.


У меня это будет строчка "!path I:\MyFirstFitnesseFixtures\bin"


После сохранения в доке-тесте можно будет увидеть


Кстати так же можно указать и ссылку на jar файл, содержащий фикстуры к примеру так "!path I:\MyFirstFitnesseFixtures\calculator.jar", но в разработке я решил пропустить этап сборки в jar файл по причине экономии времени.

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

!|calculator.fixtures.CalculatorActionFixture|
|Операция|Показания экрана после выполнения операции|
|C|0|
|1|1|
|C|0|

!|calculator.fixtures.CalculatorActionFixture|
|Операция|Показания экрана после выполнения операции|
|C|0|
|1|1|
|2|12|
|5|125|
|7|1257|
|8|12579|

Как ты заметил, мы всего лишь добавили имя класса и пакет, в котором он размещен перед каждой таблицей. Формат строки
!|path.ClassName|
Вот то, что мы увидим в результате сохранения


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


Это значит, что фикстура отработана, но данные таблицы проигнорены. Еще бы - классы фикстур у нас пустые совсем...

Базовый класс fit.Fixture содержит некоторое число методов, которые можно переопределить в наследниках. Сейчас нас интересует метод doRow а так же конструктор. Переопределим их.

package calculator.fixtures;

import fit.Fixture;
import fit.Parse;

public class CalculatorActionFixture extends Fixture {
 
    public CalculatorActionFixture() {
        super();
    }
 
    @Override
    public void doRow(Parse row) {
        super.doRow(row);
    } 
}

Поставим внутри методов по brеakpoint.

Теперь нам надо сделать возможным debug фикстур. Делается это так. Так же, как мы прописывали где находятся java class файлы пропишем строчку
!define COMMAND_PATTERN {java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=1044 -cp %p %m}


дальше настроим remote application debug в IDE, но пока запускать не будем


Теперь нажмем кнопку "Test" на документе-тесте


На этот раз выполнение тестов остановится в самой начальной фазе и не завершится, пока мы не подключим debugger IDE


После подключения IDE остановится в нашем breakpint


Поиграся немного.

Можно заметить, что конуструктор вызывается непосредственно перед каждой строкой
!|calculator.fixtures.CalculatorActionFixture|
А метод doRow для каждой последующей строчки таблицы.

Это FIT чудит. FIT - это фреймворк, который парсит HTML страничку, полученную из вики кода и встречая там табличку находит в сответствиее ей java класс наследник Fixture, который и запускает специальным образом - по одному экземпляру на одну таблицу, по одному методу doRow на <tr>...</td> пару тегов.

Как я говорил раньше есть и другие методы базового класса Fixture, которые можно переопределить. К примеру, метод doCell выполняется для каждой пары <td>...</td> в пределах одной row. Вот его сигнатура
public void doCell(Parse cell, int columnNumber) {
    super.doCell(cell, columnNumber);
}
А вот список всех остльных методов, как по мне - поле для экспериментов!


Если заглянуть вовнутрь параметра row метода doRow то можно увидеть его непростую структуру


Но на самом деле все просто - для таблички


Полезные значения будут такими:

первая колонка
row.parts.body = 'Операция'
row.parts.more.body = 'Показания экрана после выполнения операции'

следующая колонка 
row.more.parts.body = 'C'
row.more.parts.more.body = '0'

следующая колонка 
row.more.more.parts.body = '1'
row.more.more.parts.more.body = '1'

следующая колонка 
row.more.more.more.parts.body = '2'
row.more.more.more.parts.more.body = '12'

и так далее. Самое неприятное в написании фикстуры с нуля (а можно воспользоваться одним из подготовленных наследников Fixture, но об этом позже) - это то, что придется барахтаться среди этих more.more.more... Но ничего!

А вот пример того, как получается обратная связь

@Override
    public void doRow(Parse row) {
        Parse cell = row.parts;
        for (int i = 0; cell != null; i++, cell = cell.more) {   
            if (i%2 == 0) {
                cell.addToTag(" class=\"pass\"");       
                cell.addToBody(Fixture.label("OK"));
            } else {
                cell.addToTag(" class=\"fail\"");       
                cell.addToBody(Fixture.label("ERR"));
            }
        }
        super.doRow(row);
    }

и вот что в результате запуска теста случится с табличкой


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

На этом низкуровневое программирование заканчиваем. Дальше я расскажу про набор классов-наследников Fixture, которые позволяют отвлечься от row.more.more.more.parts.more.body...

Но это в следующий раз...

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