Несколько месяцев назад состоялся релиз Cucumber JVM 3.0.0. Новая версия призвана сделать работу с данным BDD фреймвоком более очевидной и гибкой. В данной статье я расскажу об изменениях и новых фичах, а также приведу примеры их использования.
В третьей версии Cucumber JVM стал доступен Cucumber Expressions — простой язык выражений для поиска подстрок в тексте. В отличие от регулярных выражений этот язык оптимизирован для удобочитаемости, что в контексте Cucumber'а имеет больший смысл. Если вам нужна гибкость, то по-прежнему можно воспользоваться регулярными выражениями.
Например, у нас есть следующая фича:
Для получения аргументов из неё можно воспользоваться следующим описанием шагов:
Как видно из примера Cucumber Expressions состоит из двух фигурных скобок с указанием типа передаваемого значения.
«Из коробки» доступна передача следующих типов:
{string} соответствует строке в кавычках, а {word} — одному слову без кавычек (на момент написания статьи в выражение {word} можно передавать только слова, написанные латинскими буквами).
Можно создать свой тип данных, например мы хотим передавать из фичи объект класса LocalDate:
Для этого в пакете, указанном в glue, необходимо создать класс, реализующий интерфейс TypeRegistryConfigurer, и через него добавить свой тип данных в реестр:
Класс, реализующий интерфейс TypeRegistryConfigurer, в проекте должен быть один, иначе будет выброшено исключение.
Также Cucumber Expressions позволяет в скобках указать необязательный текст:
Внутри скобок нельзя использовать пробелы (на момент написания статьи поддерживается только латинский алфавит).
Альтернативный текст задается через косую черту:
Экранировать {} и () можно обратной косой чертой.
В одном определении шага нельзя одновременно использовать Регулярные выражения и Cucumber-выражения.
В первой и второй версии Cucumber для определения типа передаваемых данных использовались регулярные выражения и библиотека XStreamsConverters. В третьей версии Cucumber разработчики отказались от использования библиотеки XStreamsConverters.
Обоснованием для отказа от XStreamConverters была плохая документация, отсутствие возможности использования сторонних мапперов объектов, а также отсутствие поддержки Java 9.
Аннотации Delimiter, Format, Transformer и другие аннотации из XStream больше не работают. Вместо них теперь необходимо использовать ParameterType или DataTableType.
Тип данных DataTable также подвергся изменениям.
Как и в предыдущих версиях Cucumber 3 без проблем справляется с преобразованием DataTable с одним столбцом в List, с двумя столбцами в Map и т.п. Но это работает, только если преобразовывать данные в один из следующих типов: String, Integer, Float, Double, Byte, Short, Long, BigInteger или BigDecimal.
Если же вы хотите создать из DataTable объект какого-то другого класса, то вам, как и в случае пользовательского типа данных, необходимо написать свой преобразователь:
Преобразователь добавляется в реестр таким же образом, как и в примере с пользовательским типом данных:
Еще одно нововведение — пре и пост степы. Теперь можно определять хуки, которые будут вызываться до и/или после каждого шага сценария.
Step hooks работают по тем же правилам, что и хуки, относящиеся к уровню сценария:
В заключение хотелось бы сказать, что даже если вы использовали в своих проектах возможности XStream и с переходом на новую версию Cucumber необходимо будет внести небольшие доработки, рекомендую это сделать. Cucumber поддерживается и активно развивается, а новые фичи делают его использование все более гибким и понятным.
Ссылки:
> Примеры из статьи
> Моя статья по первой версии Cucumber JVM
Реализация Cucumber Expressions
В третьей версии Cucumber JVM стал доступен Cucumber Expressions — простой язык выражений для поиска подстрок в тексте. В отличие от регулярных выражений этот язык оптимизирован для удобочитаемости, что в контексте Cucumber'а имеет больший смысл. Если вам нужна гибкость, то по-прежнему можно воспользоваться регулярными выражениями.
Например, у нас есть следующая фича:
# language: ru
Функция: Передача аргументов различных типов
Сценарий:
* передадим в метод шага целое число 15
* передадим в метод шага "текст"
* передадим в метод шага hello
Для получения аргументов из неё можно воспользоваться следующим описанием шагов:
@Допустим("передадим в метод шага целое число {int}")
public void giveInt(Integer int1) {
System.out.println(int1);
}
@Допустим("передадим в метод шага {string}")
public void giveString(String string) {
System.out.println(string);
}
@Допустим("передадим в метод шага {word}")
public void giveWord(String string) {
System.out.println(string);
}
Как видно из примера Cucumber Expressions состоит из двух фигурных скобок с указанием типа передаваемого значения.
«Из коробки» доступна передача следующих типов:
- {int}
- {float}
- {string}
- {word}
- {biginteger}
- {bigdecimal}
- {byte}
- {short}
- {long}
- {double}
{string} соответствует строке в кавычках, а {word} — одному слову без кавычек (на момент написания статьи в выражение {word} можно передавать только слова, написанные латинскими буквами).
Можно создать свой тип данных, например мы хотим передавать из фичи объект класса LocalDate:
# language: ru
Функция: Передача пользовательского типа
Сценарий:
* передадим в метод дату 01.06.2018
@Допустим("передадим в метод дату {localdate}")
public void передадим_в_метод_дату(LocalDate localdate) {
System.out.println(localdate.format(DateTimeFormatter.ofPattern("dd-MM-yyyy")));
}
Для этого в пакете, указанном в glue, необходимо создать класс, реализующий интерфейс TypeRegistryConfigurer, и через него добавить свой тип данных в реестр:
public class TypeRegistryConfiguration implements TypeRegistryConfigurer {
@Override
public Locale locale() {
// требуется только для определения формата разделителя в float и double
return new Locale("ru");
}
@Override
public void configureTypeRegistry(TypeRegistry typeRegistry) {
// добавление в реестр определения необходимого типа
typeRegistry.defineParameterType(new ParameterType<>(
// название параметра, используемое в определении шага:
"localdate",
// регулярка, для поиска необходимого значения в фиче:
"[0-9]{2}.[0-9]{2}.[0-9]{4}",
// тип параметра:
LocalDate.class,
// функция, преобразующая входящую строку к нужному типу
(Transformer<LocalDate>) s -> {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
return LocalDate.parse(s, formatter);
}
));
}
}
Класс, реализующий интерфейс TypeRegistryConfigurer, в проекте должен быть один, иначе будет выброшено исключение.
Также Cucumber Expressions позволяет в скобках указать необязательный текст:
@Допустим("Hello, world(s)!")
public void getHello() {
System.out.println("Hello world!");
}
Внутри скобок нельзя использовать пробелы (на момент написания статьи поддерживается только латинский алфавит).
Альтернативный текст задается через косую черту:
@Допустим("основной/альтернативный текст")
public void getAlternative() {
System.out.println("Hello world!");
}
# language: ru
Функция: Основной и альтернативный текст
Сценарий:
* альтернативный текст
* основной текст
Экранировать {} и () можно обратной косой чертой.
В одном определении шага нельзя одновременно использовать Регулярные выражения и Cucumber-выражения.
Отказ от XStream
В первой и второй версии Cucumber для определения типа передаваемых данных использовались регулярные выражения и библиотека XStreamsConverters. В третьей версии Cucumber разработчики отказались от использования библиотеки XStreamsConverters.
Обоснованием для отказа от XStreamConverters была плохая документация, отсутствие возможности использования сторонних мапперов объектов, а также отсутствие поддержки Java 9.
Аннотации Delimiter, Format, Transformer и другие аннотации из XStream больше не работают. Вместо них теперь необходимо использовать ParameterType или DataTableType.
DataTable
Тип данных DataTable также подвергся изменениям.
Как и в предыдущих версиях Cucumber 3 без проблем справляется с преобразованием DataTable с одним столбцом в List, с двумя столбцами в Map и т.п. Но это работает, только если преобразовывать данные в один из следующих типов: String, Integer, Float, Double, Byte, Short, Long, BigInteger или BigDecimal.
Если же вы хотите создать из DataTable объект какого-то другого класса, то вам, как и в случае пользовательского типа данных, необходимо написать свой преобразователь:
# language: ru
Функция: Передача пользовательского типа через DataTable
Сценарий:
Допустим у нас есть пользователи
| Василий | Чапаев | 09.02.1887 |
| Пётр | Исаев | 23.02.1890 |
User.java
import java.time.LocalDate;
public class User {
private String firstName;
private String lastName;
private LocalDate birthDay;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public LocalDate getBirthDay() {
return birthDay;
}
public void setBirthDay(LocalDate birthDay) {
this.birthDay = birthDay;
}
@Override
public String toString() {
return "User{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", birthDay=" + birthDay +
'}';
}
}
@Допустим("у нас есть пользователи")
public void у_нас_есть_пользователи(List<User> users) {
System.out.println(users);
}
Преобразователь добавляется в реестр таким же образом, как и в примере с пользовательским типом данных:
public class TypeRegistryConfiguration implements TypeRegistryConfigurer {
@Override
public Locale locale() {
return new Locale("ru");
}
@Override
public void configureTypeRegistry(TypeRegistry typeRegistry) {
// в этот раз в реестр добавляем DataTableType
typeRegistry.defineDataTableType(new DataTableType(
User.class,
(TableRowTransformer<User>) list -> {
User user = new User();
user.setFirstName(list.get(0));
user.setLastName(list.get(1));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
user.setBirthDay(LocalDate.parse(list.get(2), formatter));
return user;
}
));
}
}
Before и After Step Hooks
Еще одно нововведение — пре и пост степы. Теперь можно определять хуки, которые будут вызываться до и/или после каждого шага сценария.
Step hooks работают по тем же правилам, что и хуки, относящиеся к уровню сценария:
- на хуки можно навешивать тэги для их запуска только в определенных сценариях;
- можно устанавливать очередность выполнения хуков;
- AfterStep выполнится даже если шаг, после которого он должен был выполниться, сломается.
# language: ru
Функция: Фикстуры
@hooks
Сценарий: вызов фикстур до и после сценария, а также перед и после каждого шага
Дано Первый шаг
Когда Второй шаг
Тогда Третий шаг
@only_scenario_hooks
Сценарий: вызов фикстур только до и после сценария
Дано Первый шаг
Когда Второй шаг
Тогда Третий шаг
@only_step_hooks
Сценарий: вызов фикстур только перед и после каждого шага
Дано Первый шаг
Когда Второй шаг
Тогда Третий шаг
// секция not написана исключительно ради демонстрации
// выполнится только перед сценариями с тэгами hooks и only_scenario_hooks
@Before(value = "(@hooks or @only_scenario_hooks) and not @only_step_hooks")
public void before() {
System.out.println("before scenario");
}
// выполнится перед каждым шагом в сценарии с тэгом only_step_hooks
@BeforeStep(value = "@only_step_hooks")
public void beforeStep() {
System.out.println("before step");
}
// выполнится после каждого шага в сценарии с тэгом only_step_hooks
@AfterStep(value = "not(@hooks or @only_scenario_hooks) and @only_step_hooks")
public void afterStep() {
System.out.println("after step");
}
// выполнится только после сценариев с тэгами hooks и only_scenario_hooks
@After(value = "@hooks or @only_scenario_hooks")
public void after() {
System.out.println("after scenario");
}
В заключение хотелось бы сказать, что даже если вы использовали в своих проектах возможности XStream и с переходом на новую версию Cucumber необходимо будет внести небольшие доработки, рекомендую это сделать. Cucumber поддерживается и активно развивается, а новые фичи делают его использование все более гибким и понятным.
Update 23.09.2018
Сегодня зарелизили Cucumber 4.0.0. Не буду описывать все изменения, скажу только, что теперь в Cucumber Expressions нормально поддерживается Русский язык, также, что написанный мной пример с {string} и {word} теперь работать не будет. Дело в том, что {word} теперь может содержать любой непробельный символ, что вызывает конфликт при поиске шага реализации:
# language: ru
Функция: Передача аргументов различных типов
Сценарий:
* передадим в метод шага "текст"
* передадим в метод шага hello
@Допустим("передадим в метод шага {string}")
public void giveString(String string) {
System.out.println(string);
}
@Допустим("передадим в метод шага {word}")
public void giveWord(String string) {
System.out.println(string);
}
Ссылки:
> Примеры из статьи
> Моя статья по первой версии Cucumber JVM
AlexMt
А еще они отказались от поддержки closure, groovy, scala и еще ряда языков, во всяком случае силами core-команды, и ищут тех кто будет эти языки поддерживать.
savkk Автор
К теме статьи это не относится, но да, вы правы. Разработчики вынуждены были отказаться от поддержки других JVM-языков. На сколько я знаю, они стремятся поддерживать одинаковый функционал во всех реализациях Cucumber, но в связи с тем, что сейчас в core-jvm команде всего 3 человека и все они занимаются разработкой только в свободное время, а так же участвуют в других core-командах cucumber, поддерживать такое количество JVM-языков нереально.
AlexMt
Угу. Жалко вообще что новость практически незамеченной прошла, а ведь это важный релиз. В нём избавились от давнокода XStream-а, добавили произвольные словари (наконец-то!) и теперь можно создавать свои шаблоны, а это означает — прямой путь к генераторам всяких тестовых данных, формат которых теперь удобно задавать прямо в аргументе.
У нас был на прошлом проекте наколхожен такой подход, к примеру, {TIMESTAMP-5h} и мы смотрели, если есть такое ключевое слово и фигурные скобки — всё передавалось в `@Transformer ` и он дальше решал, что с этим делать. Далее обычно шла длинная цепь проверок, «какому же трансформатому этот аргумент, чёрт возьми, принадлежит» :)) Теперь это в прошлом.
Спасибо за статью! По поводу моего комментария хочу сказать что к теме статьи это и не относится напрямую, но у них в блоге новость вышла в преддверье релиза 3-го огурца, в котором они объясняли почему 3ий огурец для этих JVM-языков, скорее всего, не выйдет.