Введение
Современные проекты все чаще предъявляют высокие требования к покрытию автоматическими тестами. В наше время писать тесты не просто признак хорошего тона, но одно из требований, которое предъявляется к коду. Все чаще мы слышим такие аббревиатуры, как TDD (Test Driven Development) и BDD (Behaviour Driven Development) и многие строго следуют этим подходам в разработке.
BDD это одна из разновидностей TDD, и об этом я хотел бы написать в этой статье. Точнее не о самом BDD, а о frameworks, которые нам предоставляет индустрия на сегодняшний день. А если уж быть совсем точным, то о трех из них: spock, easyb и href="http://www.easyb.org/">cucumber.
TDD и BDD
Я не буду тут ссылаться на статьи и презентации корифеев IT индустрии. Мне запомнилась одна фраза из Twitter по поводу TDD которая засела в моем сознании, и которая на мой взгляд четко и коротко характеризует TDD подход. К сожалению дословно я её привести не могу, но смысл в ней следующий: «если вы следуете TDD, то можете быть 100% уверены, что каждая строчка кода была написана благодаря упавшему(ым) тесту(ам)». Я видел и слышал много дебатов по поводу достоинств и недостатков TDD и BDD, но а) тесты писать надо б) если код был написан благодаря упавшему тесту, то этому коду можно доверять, и с легкостью его изменять (рефакторить) не боясь испортить поведение системы.
Теперь про BDD. Появилось это явление позже и как утверждает Фаулер в статье «Mocks Aren't Stubs» благодаря так называемым мокистам. С другой стороны этот подход активно продвигают ребята из Agaile тусовки, сводя к минимуму расстояние между разработчиками, пользователями и аналитиками систем. Достигается это путем получения Executable Scenarios, иными словами, сценарии которые описывают пользователи переводятся в исполняемый тест. BDD frameworks с этим удачно справляются.
Теперь перейдем к сравнению.
Все примеры описывают один и тот же сценарий. Я опущу описание проблемы решение, которой необходимо покрыть тестами, потому как сами сценарии должны ясно описать её.
Автор статьи приводит реализации BDD в порядке возрастания симпатии к ним.
Easyb
Данный framework написан на Groovy. Как и все BDD реализации поддерживает нотацию Given-When-Then. Легко интегрируется в Continuous Integration (CI). before "init input and expected result",{ where "complete scenarios data",{ scenario "find summ within two indexes #leftIndex and #rightIndex of the array #input",{ when "calc sum between two indexes", { then "summ should be equal expected #expectedSumm", {
Вот пример сценария:
description "This story is about sqrt optimisation algorithm"
narrative "this shows sqrt optimisation", {
as a "java developer"
i want "to know how sqrt optimisation works"
so that "that I can pass google interview"
}
}
input = [[5, 10, -3, 17, 12, 1, -2, 13, -12], [5, 8, 13, 5, 21, 6, 3, 7, -2, 4, 8, 12]]
leftIndex = [2,3]
rightIndex = [5,10]
expectedSumm = [27,51]
}
given "An Sqrt algorithm implementation",{
alg = new SqrtDecompositionSum(input.toArray(new int[0]))
}
actualSum = alg.calcSummBetween(leftIndex, rightIndex)
}
actualSum.shouldBe expectedSumm
}
}
Вот как выглядит результат теста:
Тут «вылазит» первый недостаток easyb. Дело в том, что непонятно откуда взялось 2 сценария, в то время как описан 1. Если вглядеться в секцию where сценария, то можно увидеть, что подготавливается 2 набора входных и ожидаемых значений. К сожалению конструкция where не документирована даже на сайте проекта, по крайней мере я её там не нашел.
Ниже приведен пример упавшего теста-сценария
Как видим результат вполне читаем. Обратите внимание на строку actualSum.shouldBe expectedSumm
. Это sugar, который предоставляет easyb для проверки ожидаемого с актуальным результатом.
Для того чтобы запустить сценарий из IDE необходимо поставить easyb plugin.
Вторым недостатком я могу отметить то, что последний раз обновления easyb было в 2010 году, что на мой взгляд уже достаточно давно.
За подробностями обращайтесь на сайт проекта.
Spock
Spock, как и EasyB выходец из groovy. Его очень любят использовать разработчики Groovy/Grails. Наш сценарий будет выглядеть так:
class SqrtSumAlgSpecTest extends Specification {
Algorithm alg
def "Sqrt sums scenarios"(){
when:
alg = new SqrtDecompositionSum(input.toArray(new int[0]))
then:
outputSumm == alg.calcSummBetween(leftIndex, rightIndex)
where:
input | leftIndex | rightIndex | outputSumm
[5, 10, -3, 17, 12, 1, -2, 13, -12] |2 |5 |27
[5, 8, 13, 5, 21, 6, 3, 7, -2, 4, 8, 12] |3 |10 |52
}
}
Spock мне больше нравится конструкцией where. Для того, чтобы создать spock спецификацию, необходимо создать groovy класс унаследованный от spock.lang.Specification
.
Ниже приведен пример упавшего теста-сценария:
Spock, на мой взгляд, ближе к разработчику нежели к аналитику или QA инженеру, однако все равно легко читаем.
Cucumber
С Cucumber я познакомился совсем недавно, и чем больше я с ним экспериментировал, тем больше он мне нравился. В отличие от первых двух, Cucumber выходец из Ruby. Существует его реализация для Java и С#.
Сценарии на cucumber состоят из двух файлов: собственно сценарий, и его реализация на Java, C#, Ruby. Это позволяет отделить сценарий от реализации, что делает сценарии абсолютно обычным повествованием на английском языке, приведем пример
Scenario Outline: Sqrt Sums Alg Scenario Examples: Кстати, сценарии в cucumber называют features. Feature: Sqrt Sums Algorithm Feature
In order to ensure that my algorithm works
As a Developer
I want to run a quick Cuke4Duke test
Given The input array When The calc sum between , Then The summ is .
|input array |Left index |Right index|output summ|
|5, 10, -3, 17, 12, 1, -2, 13, -12 |2 |5 |27 |
|5, 8, 13, 5, 21, 6, 3, 7, -2, 4, 8, 12 |3 |10 |52
А вот реализацияpublic class SqrtsumsalgFeature {
private Algorithm alg;
private int result;
@Given ("^The input array ([\d\s\-\,]*)$")
public void theInputArray(String input) {
String[] split = input.split(",");
int[] arrayInput = new int[split.length];
for (int i = 0; i < arrayInput.length; i++) {
arrayInput[i] = Integer.valueOf(split[i].trim());
}
alg = new SqrtDecompositionSum(arrayInput);
}
@When ("^The calc sum between ([\d]*), ([\d]*)$")
public void theCalcSumBetween(int L, int R) {
result = alg.calcSummBetween(L, R);
}
@Then ("^The summ is ([\d]*).$")
public void theSummIs(int expectedResult) {
Assert.assertThat(result, is(expectedResult));
}
}
Тут нужно соблюдать Naming Conventions как в именах файлов сценария и реализации, так и в именах методов реализации и шагов сценария. Иными словами они должны соответствовать. Соответствие достигается путем использования аннотаций @Given, @When, @Then и строк регулярных выражений в качестве аргументов к аннотациям.
Используя группы регулярных выражений можно выделять аргументы методов реализации.
Ниже приведен пример прошедшего теста на cucumber
А вот пример упавшего feature
Мне по-душе разделение сценария от его реализации. Кого то может смутить использование регулярных выражений чтобы "увязать" реализацию со сценарием, однако они скрыты от пишущего сценарий, и большинство разработчиков знакомы с ними, так что этот факт я бы не стал относить к недостаткам. За информацией о cuke4duke - реализации для Java прошу зайти сюда.
Итог
Статья получилась выше среднего. Хотелось бы еще описать интеграцию с maven и Continuous Integration. Думаю, что это будет тема будущего поста.
Пишите исполняемые сценарии, это не только полезно, но и доставляет удовольствие.
Автор: semenodm