В прошлый раз мы начали писать свои фикстуры, но залезли немного в дебри. На самом деле наследоваться от Fixture тебе придется в редких случаях - тогда, когда не поможет базовый набор фикстур (уже разработанный для тебя) - это fit.ColumnFixture, fit.ActionFixture и fit.RowFixture.
Начнем с fit.ActionFixture. Для этого исходную табличку
модифицируем так
и проделаем то же с каждой табличкой типа CalculatorActionFixture.
В скомпилированном виде табличка выглядит так
Ну, что очень даже ничего. Думаю, заказчик не будет сильно против слегка изменившегося формата.
Если теперь попытаться запустить такой тест, то Fitnesse подскажет нам, что в нашем классе-фикстуре не хватает некоторых методов.
Добавим их!
Если теперь запустить на выполнение тест, то картинка изменится
А все потому что мы ожидаем (expect) одно, а получаем (actual) другое.
Принцип работы фикстуры fit.ActionFixture прост. Табличка имеет формат
Тег start - говорит о том, какой класс будет использоваться. Этот класс должен наследовать fit.Fixture и содержать в себе ряд методов method1, method2 и method3, сигнатуры которых определяются по тегам enter/check/press предшествующим им:
Теперь нам надо некий калькулятор. Допустим его программеры сделали таким
Прошу простить меня за мой французский (я про интерфейс), но допустим, что разработчики поняли задачу именно так! Кстати, тут может содержать еще некоторое число багов - я основывался на тестах и в какой-то момент решил закачивать - все же не калькулятор разрабатываю. Вот кстати тесты:
Итак, есть калькулятор и есть фикстура, которая должна дергать его. Трудность в том, что интерфейсы у них разные - а значит фикстурка будет делать некоторые дополнительные преобразования.
Легко! Вот фикстура
А вот, что в результате у нас получилось.
Ой! Кажется заказчик ошибся, создавая таблички :)
Исправим эту неточность в документе-тесте и пойдем дальше.
У нас осталась еще две таблички-фикстуры, которые пока не рабочие
Отремонтируем их! Тут нам fit.ActionFixture не особо поможет. Зато у нас остались еще два базовых класса, один из которых нам наверняка поможет - это fit.ColumnFixture.
Для этого две наши таблички
модифицируем так
Так как класс фикстуры пустой
то таблички после запуска документа-теста выглядят так (ignored)
чтобы таблички ожили, необходимо реализовать класс-фикстуру
после чего таблички заговорят
Принцип работы фикстуры fit.ColumnFixture прост.
Первая колонка указывает как будет происходить взаимодействие с классом path.SomeMyColumnFixture.
fieldname1, fieldname2, fieldname3 - public поля в которые последовательно будут вставляться данные из их колонок
method1, method2 - список методов результат выполнения которых (после вставки данных в поля) последовательно сравнится с данными из их колонок.
Сложно. Давай попроще - при выполнении этой фикстуры подряд будет сделано:
Хух...
Теперь, чтобы все заработало нам надо подружить фикстуру с классом Calculator.
и запустить тест
Упс! Опять ошибочка в требованиях. Ну уж этот заказчик! Спешит, блин!
После исправлений документа-теста мы добились заветной зеленой полосы.
На этом пока все. Исходный код калькулятора, фикстур и документ-тест в wiki коде можно взять тут.
Позже чуть расскажу, как пользоваться базовым классом fit.RowFixture для построения третьего типа фикстур. А так же разберем как в Fitnesse делаются так привычные нам из jUnit SetUp, TearDown и Suite.
Начнем с fit.ActionFixture. Для этого исходную табличку
!|calculator.fixtures.CalculatorActionFixture| |Операция|Показания экрана после выполнения операции| |С|0| |1|1| |С|0|
модифицируем так
!|fit.ActionFixture| |start|calculator.fixtures.CalculatorActionFixture| |enter|button|С| |check|display|0| |enter|button|1| |check|display|1| |enter|button|С| |check|display|0|
и проделаем то же с каждой табличкой типа CalculatorActionFixture.
В скомпилированном виде табличка выглядит так
Ну, что очень даже ничего. Думаю, заказчик не будет сильно против слегка изменившегося формата.
Если теперь попытаться запустить такой тест, то Fitnesse подскажет нам, что в нашем классе-фикстуре не хватает некоторых методов.
Добавим их!
package calculator.fixtures; import fit.Fixture; public class CalculatorActionFixture extends Fixture { public String display() { return ""; } public void button(String buttons) { } }
Если теперь запустить на выполнение тест, то картинка изменится
А все потому что мы ожидаем (expect) одно, а получаем (actual) другое.
Принцип работы фикстуры fit.ActionFixture прост. Табличка имеет формат
!|fit.ActionFixture| |start|package.ClassName| |enter|method1|someData1| |check|method2|someData2| |press|method3|someData3|
Тег start - говорит о том, какой класс будет использоваться. Этот класс должен наследовать fit.Fixture и содержать в себе ряд методов method1, method2 и method3, сигнатуры которых определяются по тегам enter/check/press предшествующим им:
- для enter - public void method1(String data); // вводим данные в фикстуру
- для check - public String method2(); // тут ActionFixture проведет сравнение того, что указано в таблице и того что вернет method2
- для press - public void method3(); // просим фикстуру сделать что-то
Теперь нам надо некий калькулятор. Допустим его программеры сделали таким
package calculator.fixtures; import java.math.BigDecimal; public class Calculator { private static final String ZERRO = "0"; private static final int NONE = 3; private static final int PLUS = 2; private static final int MINUS = 1; private String memory; private String current; private String display; private boolean willCleared; private int operation; public Calculator() { pressClear(); } public String getDisplayValue() { return display; } private void pressNum(int number) { String numberString = String.valueOf(number); if (current.equals(ZERRO) || willCleared) { current = numberString; } else { current = current + numberString; } display = current; willCleared = false; } public void press1() { pressNum(1); } public void press2() { pressNum(2); } public void press3() { pressNum(3); } public void press4() { pressNum(4); } public void press5() { pressNum(5); } public void press6() { pressNum(6); } public void press7() { pressNum(7); } public void press8() { pressNum(8); } public void press9() { pressNum(9); } public void press0() { pressNum(0); } public void pressDot() { if (current.indexOf('.') == -1) { current = current + "."; display = current; } } public void pressClear() { display = ZERRO; memory = ZERRO; current = ZERRO; operation = NONE; willCleared = false; } public void pressPlus() { pressResult(); operation = PLUS; willCleared = true; } public void pressMinus() { pressResult(); operation = MINUS; willCleared = true; } public void pressResult() { switch (operation) { case PLUS: memory = plus(memory, current); break; case MINUS: memory = minus(memory, current); break; case NONE: memory = current; return; } operation = NONE; display = memory; current = ZERRO; } public String plus(String operand1, String operand2) { return new BigDecimal(operand1).add(new BigDecimal(operand2)).toString(); } public String minus(String operand1, String operand2) { return new BigDecimal(operand1).add(new BigDecimal(operand2).negate()).toString(); } }
Прошу простить меня за мой французский (я про интерфейс), но допустим, что разработчики поняли задачу именно так! Кстати, тут может содержать еще некоторое число багов - я основывался на тестах и в какой-то момент решил закачивать - все же не калькулятор разрабатываю. Вот кстати тесты:
package calculator.fixtures; import static org.junit.Assert.assertEquals; import org.junit.Test; public class CalculatorTest { @Test public void testDefaultValue() { Calculator calculator = new Calculator(); assertEquals("0", calculator.getDisplayValue()); } @Test public void testEnterNumber() { Calculator calculator = new Calculator(); calculator.press1(); calculator.press2(); calculator.press3(); calculator.press4(); calculator.press5(); calculator.press6(); calculator.press7(); calculator.press8(); calculator.press9(); calculator.press0(); assertEquals("1234567890", calculator.getDisplayValue()); } @Test public void testTryToFillMabyZerros() { Calculator calculator = new Calculator(); calculator.press0(); calculator.press0(); calculator.press0(); assertEquals("0", calculator.getDisplayValue()); } @Test public void testTryToFillMabyZerrosWithDot() { Calculator calculator = new Calculator(); calculator.press0(); calculator.pressDot(); assertEquals("0.", calculator.getDisplayValue()); calculator.press0(); calculator.press0(); calculator.press0(); assertEquals("0.000", calculator.getDisplayValue()); } @Test public void testNoMultipleDots() { Calculator calculator = new Calculator(); calculator.press0(); calculator.pressDot(); assertEquals("0.", calculator.getDisplayValue()); calculator.pressDot(); assertEquals("0.", calculator.getDisplayValue()); calculator.press0(); calculator.press0(); calculator.press0(); assertEquals("0.000", calculator.getDisplayValue()); } @Test public void testSumTwoNumbers() { Calculator calculator = new Calculator(); calculator.press1(); calculator.press2(); calculator.press3(); calculator.pressPlus(); calculator.press3(); calculator.press2(); calculator.press1(); assertEquals("321", calculator.getDisplayValue()); calculator.pressResult(); assertEquals("444", calculator.getDisplayValue()); } @Test public void testSumThreeNumbers() { Calculator calculator = new Calculator(); calculator.press1(); calculator.press2(); calculator.press3(); calculator.pressPlus(); calculator.press3(); calculator.press2(); calculator.press1(); calculator.pressPlus(); calculator.press1(); calculator.press1(); calculator.press1(); calculator.pressResult(); assertEquals("555", calculator.getDisplayValue()); } @Test public void testMinTwoNumbers() { Calculator calculator = new Calculator(); calculator.press3(); calculator.press2(); calculator.press1(); calculator.pressMinus(); calculator.press1(); calculator.press2(); calculator.press3(); assertEquals("123", calculator.getDisplayValue()); calculator.pressResult(); assertEquals("198", calculator.getDisplayValue()); } @Test public void testMinThreeNumbers() { Calculator calculator = new Calculator(); calculator.press3(); calculator.press2(); calculator.press1(); calculator.pressMinus(); calculator.press1(); calculator.press2(); calculator.press3(); calculator.pressMinus(); calculator.press1(); calculator.press2(); calculator.pressResult(); assertEquals("186", calculator.getDisplayValue()); } @Test public void testMinusThenAfterPluss() { Calculator calculator = new Calculator(); calculator.press3(); calculator.press2(); calculator.press1(); calculator.pressMinus(); calculator.press1(); calculator.press2(); calculator.press3(); calculator.pressPlus(); calculator.press1(); calculator.press2(); calculator.pressResult(); assertEquals("210", calculator.getDisplayValue()); } @Test public void testMinTwoNumbersIfLessThan0() { Calculator calculator = new Calculator(); calculator.press1(); calculator.press2(); calculator.press3(); calculator.pressMinus(); calculator.press3(); calculator.press2(); calculator.press1(); assertEquals("321", calculator.getDisplayValue()); calculator.pressResult(); assertEquals("-198", calculator.getDisplayValue()); } @Test public void testSumNumWithDot() { Calculator calculator = new Calculator(); calculator.press0(); calculator.pressDot(); calculator.press0(); calculator.press0(); calculator.press1(); assertEquals("0.001", calculator.getDisplayValue()); calculator.pressPlus(); calculator.press2(); calculator.press3(); calculator.pressDot(); calculator.press3(); calculator.press0(); calculator.press2(); assertEquals("23.302", calculator.getDisplayValue()); calculator.pressResult(); assertEquals("23.303", calculator.getDisplayValue()); } @Test public void testMinusNumWithDot() { Calculator calculator = new Calculator(); calculator.press1(); assertEquals("1", calculator.getDisplayValue()); calculator.pressMinus(); calculator.press0(); calculator.pressDot(); calculator.press9(); calculator.press9(); calculator.press9(); assertEquals("0.999", calculator.getDisplayValue()); calculator.pressResult(); assertEquals("0.001", calculator.getDisplayValue()); } @Test public void testSumTwoNumbersAndPressResultTwice() { Calculator calculator = new Calculator(); calculator.press1(); calculator.press2(); calculator.press3(); assertEquals("123", calculator.getDisplayValue()); calculator.pressPlus(); calculator.press3(); calculator.press2(); calculator.press1(); assertEquals("321", calculator.getDisplayValue()); calculator.pressResult(); assertEquals("444", calculator.getDisplayValue()); calculator.pressResult(); assertEquals("444", calculator.getDisplayValue()); } @Test public void testSumTwoNumbersAndTryToWriteSomeOtherNumber() { Calculator calculator = new Calculator(); calculator.press1(); calculator.press2(); calculator.press3(); calculator.pressPlus(); calculator.press3(); calculator.press2(); calculator.press1(); assertEquals("321", calculator.getDisplayValue()); calculator.pressResult(); assertEquals("444", calculator.getDisplayValue()); calculator.press3(); calculator.press2(); calculator.press1(); assertEquals("321", calculator.getDisplayValue()); calculator.pressResult(); assertEquals("321", calculator.getDisplayValue()); } }
Итак, есть калькулятор и есть фикстура, которая должна дергать его. Трудность в том, что интерфейсы у них разные - а значит фикстурка будет делать некоторые дополнительные преобразования.
Легко! Вот фикстура
package calculator.fixtures; import fit.Fixture; public class CalculatorActionFixture extends Fixture { private Calculator calculator = new Calculator(); public String display() { return calculator.getDisplayValue(); } public void button(String buttons) { for (char button : buttons.toCharArray()) { pressButton(button); } } private void pressButton(char button) { switch (button) { case '0':calculator.press0(); break; case '1':calculator.press1(); break; case '2':calculator.press2(); break; case '3':calculator.press3(); break; case '4':calculator.press4(); break; case '5':calculator.press5(); break; case '6':calculator.press6(); break; case '7':calculator.press7(); break; case '8':calculator.press8(); break; case '9':calculator.press9(); break; case '+':calculator.pressPlus(); break; case '-':calculator.pressMinus(); break; case '=':calculator.pressResult(); break; case 'C':calculator.pressClear(); break; case '.':calculator.pressDot(); break; default: throw new IllegalArgumentException("Unexpected command " + button); } } }
А вот, что в результате у нас получилось.
Ой! Кажется заказчик ошибся, создавая таблички :)
Исправим эту неточность в документе-тесте и пойдем дальше.
У нас осталась еще две таблички-фикстуры, которые пока не рабочие
Отремонтируем их! Тут нам fit.ActionFixture не особо поможет. Зато у нас остались еще два базовых класса, один из которых нам наверняка поможет - это fit.ColumnFixture.
Для этого две наши таблички
|Операнд 1|Операнд 2|Результат| |123|456|579| |45|1|46| |0|1|1| |10|1200|1210| |555|777|1132| |Операнд 1|Операнд 2|Результат| |222|111|111| |333|3|330| |0|1|-1| |23|25|-2| |57|57|0|
модифицируем так
!|calculator.fixtures.CalculatorOperationFixture| |operand1|operand2|plus()| |123|456|579| |45|1|46| |0|1|1| |10|1200|1210| |555|777|1132| !|calculator.fixtures.CalculatorOperationFixture| |operand1|operand2|minus()| |222|111|111| |333|3|330| |0|1|-1| |23|25|-2| |57|57|0|
Так как класс фикстуры пустой
package calculator.fixtures; import fit.Fixture; public class CalculatorOperationFixture extends Fixture { }
то таблички после запуска документа-теста выглядят так (ignored)
чтобы таблички ожили, необходимо реализовать класс-фикстуру
package calculator.fixtures; import fit.ColumnFixture; public class CalculatorOperationFixture extends ColumnFixture { public String operand1; public String operand2; public String plus() { return ""; } public String minus() { return ""; } }
после чего таблички заговорят
Принцип работы фикстуры fit.ColumnFixture прост.
!|path.SomeMyColumnFixture| |fieldname1|fieldname2|fieldname3|method1()|method2()| |someData1|someData2|someData3|someData4|someData5| |someData6|someData7|someData8|someData9|someData10| |someData11|someData12|someData13|someData14|someData15|
Первая колонка указывает как будет происходить взаимодействие с классом path.SomeMyColumnFixture.
fieldname1, fieldname2, fieldname3 - public поля в которые последовательно будут вставляться данные из их колонок
method1, method2 - список методов результат выполнения которых (после вставки данных в поля) последовательно сравнится с данными из их колонок.
Сложно. Давай попроще - при выполнении этой фикстуры подряд будет сделано:
- fixture = new path.SomeMyColumnFixture();
- fixture.fieldname1 = "someData1"
- fixture.fieldname2 = "someData2"
- fixture.fieldname3 = "someData3"
- check that fixture.method1() == someData4
- check that fixture.method2() == someData5
- fixture.fieldname1 = "someData6"
- fixture.fieldname2 = "someData7"
- fixture.fieldname3 = "someData8"
- check that fixture.method1() == "someData9"
- check that fixture.method2() == "someData10"
- fixture.fieldname1 = "someData11"
- fixture.fieldname2 = "someData12"
- fixture.fieldname3 = "someData13"
- check that fixture.method1() == "someData14"
- check that fixture.method2() == "someData15"
Хух...
Теперь, чтобы все заработало нам надо подружить фикстуру с классом Calculator.
package calculator.fixtures; import fit.ColumnFixture; public class CalculatorOperationFixture extends ColumnFixture { private Calculator calculator = new Calculator(); public String operand1; public String operand2; public String plus() { return calculator.plus(operand1, operand2); } public String minus() { return calculator.minus(operand1, operand2); } }
и запустить тест
Упс! Опять ошибочка в требованиях. Ну уж этот заказчик! Спешит, блин!
После исправлений документа-теста мы добились заветной зеленой полосы.
На этом пока все. Исходный код калькулятора, фикстур и документ-тест в wiki коде можно взять тут.
Позже чуть расскажу, как пользоваться базовым классом fit.RowFixture для построения третьего типа фикстур. А так же разберем как в Fitnesse делаются так привычные нам из jUnit SetUp, TearDown и Suite.