Роль модульного тестирования тяжело переоценить, но теория тестирования не стоит на месте. Еще не все успели привыкнуть к хипстерскому понятию TDD, как на всех углах звучит очередное трех-буквенное сокращение BDD. Исчерпывающее описание того, что же такое BDD, можно найти в статье Введение в BDD. В данной статье речь пойдет о фреймворке cucumber, позволяющем наглядно воплотить в жизнь те идеи, которые заложены в тестировании через поведение. |
Cucumber
Cucumber - библиотека для тестирования, которая предлагает описывать сценарии тестирования на естественном языке в обычном текстовом файле с расширением .feature:Feature: Calculator Scenario Outline: Sum of the two numbers Given two numbers <a> and <b> When we try to find sum of our numbers Then result should be <result> Examples: | a | b | result | | 3 | 2 | 5 |
И связывать их с реализующим их кодом:
public class CalculatorSteps {
private Calculator calc;
double a;
double b;
double result;
@Given("^two numbers (\\d) and (\\d)")
public void given(double a, double b) {
this.a = a;
this.b = b;
this.calc = new Calculator();
}
@When("^we try to find sum of our numbers")
public void when() {
result = calc.sum(a, b);
}
@Then("^result should be (\\d)")
public void then(double res) {
Assert.assertEquals(res, result, 0.0001);
}
}
Основой описания сценария тестирования являются шаги, такие как Given, When, Then. Каждому шагу соответствует аннотация, которая с помощью регулярного выражения связывает метод, над которым объявлена, со строкой в текстовом описании сценария.
Шаги тестирования группируются в сценарии (Scenario), которые в свою очередь описывают некоторую функциональность (Feature).
Структура проекта
src ├── main │ ├── java │ │ └── ru │ │ └── dokwork │ │ └── cucumber │ │ └── Calculator.java │ └── resources └── test ├── java │ └── ru │ └── dokwork │ └── cucumber │ ├── CalculatorSteps.java │ └── CucumberTestRunner.java └── resources └── ru └── dokwork └── cucumber └── calculator.feature
Интеграция с популярными библиотеками тестирования
Cucumber прекрасно интегрируется в существующие библиотеки для запуска тестов, такие как JUnit и TestNG.
Интеграция с JUnit
Для запуска сценариев с помощью JUnit, в проект необходимо добавить зависимость от, собственно, Cucumber-а и библиотеки для его интеграции с JUnit:
<!-- Реализация Cucumber для Java -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.1.8</version>
</dependency>
<!-- Библиотека для интеграции Cucumber с JUnit -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.1.8</version>
</dependency>
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
public class CucumberTestRunner {
}
Интеграция с TestNG
В случае с TestNG, CucumberTestRunner должен наследоваться от AbstractTestNGCucumberTests:import cucumber.api.testng.AbstractTestNGCucumberTests;
public class CucumberTests extends AbstractTestNGCucumberTests {
}
<!-- Реализация Cucumber для Java -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.1.8</version>
</dependency>
<!-- Библиотека для интеграции Cucumber с JUnit -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-testng</artifactId>
<version>1.1.8</version>
</dependency>
Способы описать параметры
В самом начале статьи я привел пример описания параметризованного сценария. Возможность описывать аргументы непосредственно в текстовом файле - очень мощная фича cucumber!Этот фреймворк предоставляет несколько механизмов описания параметров тестов прямо в текстовом файле.
Первый - это группы в регулярном выражении, связывающем описание шага с его реализацией:
Feature: Example of taking the test arguments Scenario: Take argument from string Given some number value '12.3' Given some string value 'Hello world!' Given some date value 01.08.2015
public class ExampleSteps {
@Given("^some number value '(.*)'")
public void givenNumber(double number) {
System.out.println(number);
}
@Given("^some string value '(.*)'")
public void givenString(String str) {
System.out.println(str);
}
@Given("^some date value (.*)")
public void givenDate(@Format("dd.MM.yyyy") Date date) {
System.out.println(date);
}
}
12.3 Hello world! Sat Aug 01 00:00:00 MSK 2015
Given some list of values |a| |b| |c|
@Given("^some list of values")
public void givenList(List<String> list) {
for (String s : list) {
System.out.println(s);
}
}
a b c
Given some map of values |a|1| |b|2| |c|3|
@Given("^some map of values")
public void givenMap(Map<String, Integer> map) {
for (Map.Entry entry : map.entrySet()) {
System.out.println(entry);
}
}
a=1 b=2 c=3
Scenario Outline: Take few arguments from table Given argument <a>, argument <b> Examples: | a | b | | 1 | one | | 2 | two |
@Given("^argument (.*), argument (.*)")
public void givenFewArguments(int a, String b) {
System.out.println(a + "\t" + b);
}
1 one 2 two
На этом функциональность библиотеки не заканчивается и Cucumber предлагает описывать в качестве аргументов целые классы!
Given some users | name | age | | Jon | 18 | | Anna | 23 |
public class User {
String name;
int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Given("^some users$")
public void givenSomeUsers(List<User> users) throws Throwable {
for (User user : users) {
System.out.println(user);
}
}
User{name='Jon', age=18} User{name='Anna', age=23}
Предустановки
В начале каждого сценария cucumber запускает шаги описанные в блоке background:
Также есть возможность определить методы, выполняющиеся до и после каждого шага. Для этого существуют аннотации @Before и @After:
Аннотаций, аналогичных @BeforeClass и @AfterClass в JUnit, в cucumber нет. Но если они очень понадобятся, можно использовать обходной путь:
Feature: Example Background: prepare for scenario
Given any action before every scenario
public class ExampleSteps {
@Before
public void setUp() {
}
@After
public void tearDown() {
}
}
Feature: Example Background: do it at onece Given do something at once
public class ExampleSteps {
boolean isFirstExecution = true;
@Given("^do something at once")
public void initialization(String str) {
if (isFirstExecution) {
// do something...
isFirstExecution = false;
}
}
}
Не просто Cucumber - Огурец!
Еще одной эффектной особенностью Cucumber, является поддержка множества языков при описании сценариев. Среди них есть и Русский!# language: ru Функционал: Калькулятор Структура сценария: Суммирование двух чисел Допустим дано два числа <a> и <b> Если сложить их То получим <результат> Примеры: | a | b | результат | | 3 | 2 | 5 |
@Дано("^дано два числа (-?\\d+.?\\d*) и (-?\\d+.?\\d*)")
public void given(double a, double b) {
...
}
@Если("^сложить их")
public void when_sum() {
...
}
@Тогда("^получим (-?\\d+.?\\d*)")
public void then(double res) {
...
}
"name": "Russian", "native": "русский", "feature": "Функция|Функционал|Свойство", "background": "Предыстория|Контекст", "scenario": "Сценарий", "scenario_outline": "Структура сценария", "examples": "Примеры", "given": "*|Допустим|Дано|Пусть", "when": "*|Если|Когда", "then": "*|То|Тогда", "and": "*|И|К тому же|Также", "but": "*|Но|А"
Не всем огурцы по нраву...
Суть презентации, на мой взгляд:
если вы стремитесь к тому, чтобы вашим фреймворком могли пользоваться даже дебилы, именно они им и будут пользоваться.
...но
Если подходить к пользованию Cucumber с умом и к месту, можно поиметь не мало счастья.Предложенный автором статьи "Введение в BDD" подход к написанию тестов выглядит действительно многообещающим. Но на практике имена из серии shouldDoSomething очень трудно воспринимаются при чтении кода и требуют достаточно много усилий для того, чтобы понять, какой именно смысл вложен в их название. Немногим лучше обстоят дела в Scala с фреймворком specs2. Но код тестов все еще остается трудно читаемый.
Пример теста на spec2
Почему на мой взгляд важно, чтобы код тестов был прост для понимания? При анализе отчетов о прогоне тестов, найдя упавший тест, первый вопрос на который вам надо найти ответ: "а что, собственно, тест делает?", и только потом разбираться с тем, как он это делает. В моем представлении Cucumber - отличное решение, позволяющее минимизировать усилия и время для ответа на первый вопрос, и сосредоточиться на решении более важного, второго.
Замечу, что если речь идет о модульных тестах, которые по своему определению просты и компактны, то использование Cucumber скорее всего будет лишним. Но в случае больших и подчас запутанных функциональных тестов, очень важно, чтобы их сценарии были описаны в простом и строго структурированном виде. И именно здесь Cucumber может проявить себя с лучшей стороны!
2 комментария:
Вова, кто бы мог подумать, что в поиске штук по cucumberjs и тестирования ангуляров мне выдаст эту статью)) Жаль она слишком обзорная
Познавательно, спасибо!
Отправить комментарий