Cucumber 3 + Java

    Несколько месяцев назад состоялся релиз Cucumber JVM 3.0.0. Новая версия призвана сделать работу с данным BDD фреймвоком более очевидной и гибкой. В данной статье я расскажу об изменениях и новых фичах, а также приведу примеры их использования.

    Реализация 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

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 5

      0
      А еще они отказались от поддержки closure, groovy, scala и еще ряда языков, во всяком случае силами core-команды, и ищут тех кто будет эти языки поддерживать.
        0
        К теме статьи это не относится, но да, вы правы. Разработчики вынуждены были отказаться от поддержки других JVM-языков. На сколько я знаю, они стремятся поддерживать одинаковый функционал во всех реализациях Cucumber, но в связи с тем, что сейчас в core-jvm команде всего 3 человека и все они занимаются разработкой только в свободное время, а так же участвуют в других core-командах cucumber, поддерживать такое количество JVM-языков нереально.
          +1
          Угу. Жалко вообще что новость практически незамеченной прошла, а ведь это важный релиз. В нём избавились от давнокода XStream-а, добавили произвольные словари (наконец-то!) и теперь можно создавать свои шаблоны, а это означает — прямой путь к генераторам всяких тестовых данных, формат которых теперь удобно задавать прямо в аргументе.

          У нас был на прошлом проекте наколхожен такой подход, к примеру, {TIMESTAMP-5h} и мы смотрели, если есть такое ключевое слово и фигурные скобки — всё передавалось в `@Transformer ` и он дальше решал, что с этим делать. Далее обычно шла длинная цепь проверок, «какому же трансформатому этот аргумент, чёрт возьми, принадлежит» :)) Теперь это в прошлом.

          Спасибо за статью! По поводу моего комментария хочу сказать что к теме статьи это и не относится напрямую, но у них в блоге новость вышла в преддверье релиза 3-го огурца, в котором они объясняли почему 3ий огурец для этих JVM-языков, скорее всего, не выйдет.
        –1

        Четыре дня назад и Cucumber 4.0 зарелизили)

          0

          Да, вы правы, об этом я делал заметку в

        Only users with full accounts can post comments. Log in, please.