Роль модульного тестирования тяжело переоценить, но теория тестирования не стоит на месте. Еще не все успели привыкнуть к хипстерскому понятию 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);
}
}
Код класса Calculator
public class Calculator {
public double sum(double a, double b) {
return a + b;
}
public double pow(double a, double b) {
return a * b;
}
}
Основой описания сценария тестирования являются шаги, такие как Given, When, Then. Каждому шагу соответствует аннотация, которая с помощью регулярного выражения связывает метод, над которым объявлена, со строкой в текстовом описании сценария.
Шаги тестирования группируются в сценарии (Scenario), которые в свою очередь описывают некоторую функциональность (Feature).
Структура проекта
src
├── main
│ ├── java
│ │ └── com
│ │ └── programming086
│ │ └── cucumber
│ │ └── Calculator.java
│ └── resources
└── test
├── java
│ └── com
│ └── programming086
│ └── cucumber
│ ├── CalculatorSteps.java
│ └── CucumberTestRunner.java
└── resources
└── com
└── programming086
└── cucumber
└── calculator.feature
Основные файлы в cucumber - это текстовый .feature файл с описанием сценариев и .java файлы с описанием реализации шагов выполнения сценариев. Никаких обязательств, кроме расширения, на имена этих файлов не накладывается. Но отсюда следует еще один момент - шаг, описанный в одном текстовом файле будет искаться во всех java-реализациях. Т.е. надо понимать, что две реализации одинаково описанных шагов недопустимы!Интеграция с популярными библиотеками тестирования
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 -->
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.1.8</version>
</dependency>
Для Cucumber реализован свой org.junit.runner.Runner:
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 {
}
Как и в случае с JUnit, вы должны добавить в зависимости проекта библиотеку интеграции Cucumber с TestNG:
<!-- Реализация Cucumber для Java -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.1.8</version>
</dependency>
<!-- Библиотека для интеграции Cucumber с TestNG -->
<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Не плохо! Но что, если надо выполнить один и тот же сценарий с разными аргументами? На этот случай в Cucumber есть возможно перечислить все возможные значения аргумента в ASCII таблице:
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Для сценария с несколькими аргументами и вариантами, можно использовать ASCII таблицу с именованными столбцами:
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Обратите внимание на описание сценария, при реализации данного подхода. Вместо ключевого слова Scenario здесь используется Scenario Outline.
На этом функциональность библиотеки не заканчивается и 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}
Здесь стоит обратить внимание на то, что видимость полей в классе User не имеет значения, а для его инстанцирования используется хитрая ReflectionFactory, что приводит к тому, что конструктор вызван не будет!.Предустановки
В начале каждого сценария cucumber запускает шаги описанные в блоке background:
Feature: Example
Background: prepare for scenario
Given any action before every scenario
Также есть возможность определить методы, выполняющиеся до и после каждого шага. Для этого существуют аннотации @Before и @After:
public class ExampleSteps {
@Before
public void setUp() {
}
@After
public void tearDown() {
}
}
Аннотаций, аналогичных @BeforeClass и @AfterClass в JUnit, в cucumber нет. Но если они очень понадобятся, можно использовать обходной путь:
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": "*|Но|А"Полный перечень соответствия для всех поддерживаемых языков можно найти в файле i18n.json, который находится в gherkin-2.12.2.jar в пакете gherkin.
Не всем огурцы по нраву...
The Dangers of Cucumber from Þorgeir Ingvarsson
Суть презентации, на мой взгляд:
если вы стремитесь к тому, чтобы вашим фреймворком могли пользоваться даже дебилы, именно они им и будут пользоваться.
...но
Если подходить к пользованию Cucumber с умом и к месту, можно поиметь не мало счастья.Предложенный автором статьи "Введение в BDD" подход к написанию тестов выглядит действительно многообещающим. Но на практике имена из серии shouldDoSomething очень трудно воспринимаются при чтении кода и требуют достаточно много усилий для того, чтобы понять, какой именно смысл вложен в их название. Немногим лучше обстоят дела в Scala с фреймворком specs2. Но код тестов все еще остается трудно читаемый.
Пример теста на spec2
Почему на мой взгляд важно, чтобы код тестов был прост для понимания? При анализе отчетов о прогоне тестов, найдя упавший тест, первый вопрос на который вам надо найти ответ: "а что, собственно, тест делает?", и только потом разбираться с тем, как он это делает. В моем представлении Cucumber - отличное решение, позволяющее минимизировать усилия и время для ответа на первый вопрос, и сосредоточиться на решении более важного, второго.
Замечу, что если речь идет о модульных тестах, которые по своему определению просты и компактны, то использование Cucumber скорее всего будет лишним. Но в случае больших и подчас запутанных функциональных тестов, очень важно, чтобы их сценарии были описаны в простом и строго структурированном виде. И именно здесь Cucumber может проявить себя с лучшей стороны!
Немає коментарів:
Дописати коментар