Pull to refresh

Тестирование с использованием BDD

Reading time5 min
Views159K

Введение


Современные проекты все чаще предъявляют высокие требования к покрытию автоматическими тестами. В наше время писать тесты не просто признак хорошего тона, но одно из требований, которое предъявляется к коду. Все чаще мы слышим такие аббревиатуры, как TDD (Test Driven Development) и BDD (Behaviour Driven Development) и многие строго следуют этим подходам в разработке.
BDD это одна из разновидностей TDD, и об этом я хотел бы написать в этой статье. Точнее не о самом BDD, а о frameworks, которые нам предоставляет индустрия на сегодняшний день. А если уж быть совсем точным, то о трех из них: spock, easyb и 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).
Вот пример сценария:
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"
}

before "init input and expected result",{
}

where "complete scenarios data",{
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]
}

scenario "find summ within two indexes #leftIndex and #rightIndex of the array #input",{
given "An Sqrt algorithm implementation",{
alg = new SqrtDecompositionSum(input.toArray(new int[0]))
}

when "calc sum between two indexes", {
actualSum = alg.calcSummBetween(leftIndex, rightIndex)
}

then "summ should be equal expected #expectedSumm", {
actualSum.shouldBe expectedSumm
}
}

Вот как выглядит результат теста:
image

Тут «вылазит» первый недостаток 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. Это позволяет отделить сценарий от реализации, что делает сценарии абсолютно обычным повествованием на английском языке, приведем пример

Feature: Sqrt Sums Algorithm Feature
In order to ensure that my algorithm works
As a Developer
I want to run a quick Cuke4Duke test

Scenario Outline: Sqrt Sums Alg Scenario
Given The input array <input array>
When The calc sum between <Left index>, <Right index>
Then The summ is &ltoutput summ>.

Examples:
|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


Кстати, сценарии в cucumber называют features.
А вот реализация

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. Думаю, что это будет тема будущего поста.
Пишите исполняемые сценарии, это не только полезно, но и доставляет удовольствие.
Tags:
Hubs:
Total votes 23: ↑20 and ↓3+17
Comments19

Articles