πŸ“œ ⬆️ ⬇️

Cucumber 3 + Java

A few months ago, the release of Cucumber JVM 3.0.0. The new version is designed to make working with this BDD framework more obvious and flexible. In this article I will talk about the changes and new features, as well as give examples of their use.

Implementing Cucumber Expressions


In the third version of Cucumber JVM, Cucumber Expressions became available - a simple expression language for searching for substrings in text. Unlike regular expressions, this language is optimized for readability, which makes more sense in the context of Cucumber. If you need flexibility, you can still use regular expressions.

For example, we have the following feature:

# language: ru :     : *       15 *     "" *     hello 

To obtain arguments from it, you can use the following description of the steps:
')
  @("      {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); } 

As can be seen from the example, Cucumber Expressions consists of two curly brackets with the type of value being transferred.

"Out of the box" is available to transfer the following types:


{string} corresponds to a string in quotes, and {word} to a single word without quotes (at the time of writing the article, only words written in Latin letters can be transferred to the expression {word}).

You can create your own data type, for example, we want to transfer an object of the LocalDate class from a feature:

 # language: ru :    : *     01.06.2018 

  @("    {localdate}") public void ___(LocalDate localdate) { System.out.println(localdate.format(DateTimeFormatter.ofPattern("dd-MM-yyyy"))); } 

To do this, in the package specified in glue, you need to create a class that implements the TypeRegistryConfigurer interface, and through it add your data type to the registry:

 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); } )); } } 

The class that implements the TypeRegistryConfigurer interface must be single in the project, otherwise an exception will be thrown.

Cucumber Expressions also allows you to specify optional text in brackets:

  @("Hello, world(s)!") public void getHello() { System.out.println("Hello world!"); } 

No spaces can be used inside parentheses (at the time of writing, only the Latin alphabet is supported).

Alternate text is specified with a slash:

  @("/ ") public void getAlternative() { System.out.println("Hello world!"); } 

 # language: ru :     : *   *   

You can escape {} and () with a backslash.

Regular expressions and Cucumber expressions cannot be used in the same step definition at the same time.

Waiver of XStream


In the first and second versions of Cucumber, regular expressions and the XStreamsConverters library were used to determine the type of data transferred. In the third version of Cucumber, developers have abandoned the use of the XStreamsConverters library.

The rationale for abandoning XStreamConverters was poor documentation, the inability to use third-party object mappers, and the lack of support for Java 9.
Annotations Delimiter, Format, Transformer, and other annotations from XStream no longer work. Instead, you must now use ParameterType or DataTableType.

Datatable


The DataTable data type has also changed.

As in previous versions, Cucumber 3 handles the transformation of a DataTable with one column to a List, with two columns to a Map, and so on. But this only works if you convert the data to one of the following types: String, Integer, Float, Double, Byte, Short, Long, BigInteger, or BigDecimal.

If you want to create an object of some other class from the DataTable, then you, as in the case of a custom data type, need to write your own converter:

 # 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); } 

The converter is added to the registry in the same way as in the example with a custom data type:

 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 and After Step Hooks


Another innovation is the pre and post of the steppe. Now you can define hooks that will be called before and / or after each step of the script.

Step hooks work according to the same rules as the script hooks:


 # 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"); } 

In conclusion, I would like to say that even if you used the capabilities of XStream in your projects, and with the transition to the new version of Cucumber, you will need to make minor improvements, I recommend it. Cucumber is maintained and actively developed, and new features make its use more flexible and understandable.

Update 09/23/2018
Today released Cucumber 4.0.0. I will not describe all changes, I will only say that now Russian language is normally supported in Cucumber Expressions, also that the example written by me with {string} and {word} will not work now. The fact is that {word} can now contain any non-whitespace character, which causes a conflict when searching for an implementation step:

 # language: ru :     : *     "" *     hello 


  @("    {string}") public void giveString(String string) { System.out.println(string); } @("    {word}") public void giveWord(String string) { System.out.println(string); } 


References:

β†’ Examples from the article
β†’ My article on the first version of the Cucumber JVM

Source: https://habr.com/ru/post/422651/


All Articles