📜 ⬆️ ⬇️

Patterns and antipatterns Cucumber BDD

Having spent a lot of man-hours on the development of autotests for several huge projects, I can say with certainty that it could be far from complete, but certainly quite a large set of practices that I want to share with everyone. So, following the footsteps of the classics, I want (I hope to see additions from you in the comments) to compile:

Cucumber BDD Script Design Patterns


Goals:


So let's go!

Sequence pattern


Symptom: There is a ready-made script for the application, but the logic in the purpose of the steps is not correct.
')
Solution: Line up the script in accordance with the sequence: Given - Action - Result.

Result: The script acquires a logical division into areas of responsibility, it becomes more understandable to an outside observer.

Application template: Consider an example:

 Given Trader Application is started
 And user clicks on File menu item
 And user clicks on File / New menu item
 Then New File dialog box should be shown

Contains actions in the Given section. As recommended by the design pattern, actions should be in the When section:

 Given Trader Application is started
 When user clicks on File menu item
 And user clicks on File / New menu item
 Then New File dialog box should be shown

Background Template


Symptom: In each file script, the same preparatory work is carried out to achieve some state of the application.

Solution: Move the general code to the Background section.

Result: Each script becomes concise and does only those actions that are written only for him. Taking out the general code, its future support becomes easy.

Pattern Application:

 Scenario: File / New menu item
   Given Trader Application is started
   When user clicks on File / New menu item
   Then New File dialog box should be shown

 Scenario: File / Open menu item
   Given Trader Application is started
   When user clicks on File / Open menu item
   Then Open File dialog box should be shown

Contains the same preconditions in the Given section. According to the recommendation of the design pattern, they should be in the Background section.

 Background:
  Given Trader Application is started
 
 Scenario: File / New menu item
   When user clicks on File menu item
   And user clicks on File / New menu item
   Then New File dialog box should be shown

 Scenario: File / Open menu item
   When user clicks on File menu item
   And user clicks on File / Open menu item
   Then Open File dialog box should be shown

Strategy Pattern


Symptom: For the same action on different entities of the same type, different definitions of action steps in their implementation are defined.

Solution: Make the action general, taking the essence as an option.

Result: The script code becomes more predictable and clear, the code for implementing actions is reduced to one method. Future code support is facilitated.

Pattern Application:

 Scenario: File / New menu item
   Given Trader Application is started
   When user clicks on File / New menu item
   Then New File dialog box should be shown

 Groovy part:
 When (~ 'user clicks on File menu item') {->
   $ ('# FileMenuItem'). Click ()
 }

 When (~ 'user clicks on File / New menu item') {->
   wait {
     $ ('# FileNewMenuItem'). Displayed
   }
   $ ('# FileNewMenuItem'). Click ()
 }

Contains the same code for the two steps. According to the recommendation of the design pattern, they should be combined into one method.

 When (~ 'user clicks on (. +)') {ControlDsl control ->
   dsl.displayed.shouldBecome (true)
   dsl.click ()
 }

Interface Template


Symptom: Different definitions of the steps of the same actions in their implementation are defined for different types of entities.

Solution: Select properties and actions on entities in separate interfaces, grouping according to general characteristics. The entities themselves in the definitions of steps should not be taken through finite types, but through interfaces.

Result: Thus, the procedure for acting on different types of entities is unified. Further, in order for the entity to support all the necessary steps, it is sufficient to implement all the necessary interfaces. At the same time, highlighting the base class above the entities of a single functional group (for example, UI elements) and implementing the interfaces in it, all successors automatically receive support for all steps above them.

Pattern Application:

 Scenario outline: File / New menu item
   Given Trader Application is started
   When user clicks on <ui element>
   And user clicks on File / New menu item
   Then <entity> should exist
   Examples:
   |  ui element |  entity |
   |  File menu item |  New File dialog |
   |  regedit icon |  HKLM /../ newfilekey |


 Groovy part:
 Then (~ '(. +) Should exist)') {Control control ->
   control.displayed.shouldBe (true)
 }

 Then (~ '(. +) Should exist)') {RegistryKey key ->
   key.shuoldNotBeNull ()
 }

It contains the same step text, but differing in the types of parameters contains a different implementation.

 Then (~ '(. +) Should exist') {ICanExist entity ->
   control.exist.shouldBecome (true)
 }

 implementation at the same time is made in the implementation of entities

Inside Template


Symptom: For entities located in different parts of their hierarchy within the application, a flat hierarchy of entity registrations is created in the cucumber bdd service code and, as a result, name intersections (Trade Ok button, Save Ok button, Are You Sure Ok button instead of just Ok button).

Solution: Implement a hierarchical registration of entities with preserving information about the nesting of some entities in others.

Result: Thus, the procedure for acting on different types of entities is unified. Further, in order for the entity to support all the necessary steps, it is sufficient to implement all the necessary interfaces. At the same time, highlighting the base class above the entities of a single functional group (for example, UI elements) and implementing the interfaces in it, all successors automatically receive support for all steps above them.

Used in conjunction with the Scope pattern.

Pattern Application:

 Scenario outline: File / New menu item
   Given Trader Application is started
   When user clicks on File menu item
   And user clicks on File / New menu item
   New user dialog box

 Groovy part:
 When (~ 'user clicks on (. +)') {Control control ->
   control.displayed.shouldBecome (true)
   control.click ()
 }

 Registration part:

 class NewFileDialog: Control {
   controls: {
       "Ok button": {$ ('# NewFileOkButton')}
   }
 }

 class OpenFileDialog: Control {
   controls: {
       "Ok button": {$ ('# OpenFileOkButton')}
   }
 }

 class Root: Control {
   controls: {
       "New File dialog box": {$ ('# NewFileDialog')}
       "Open File dialog box": {$ ('# NewFileDialog')}
   }
 }

Scope Template


Symptom: For entities that are within the hierarchy of entities at a non-zero depth, access is provided through Inside despite the fact that there is only one entity with the given name.

Solution: Use names only relative to the current context and change the context in the course of the script if the number of actions in another context is more than one.

Result: Shortening scripts, avoiding long strings of element names.

Pattern Application:

 Scenario outline: File / New menu item
   Given Trader Application is started
   When user clicks on File menu item
   And user clicks on File / New menu item
   And New File dialog box is opened <- step changes context to dialogue
   Then user clicks on Ok button <- the button is accurately identified, despite the fact that there may be several registrations in the system Ok button

 Groovy part:
 When (~ 'user clicks on (. +)') {Control control ->
   control.displayed.shouldBecome (true)
   control.click ()
 }

 Registration part:

 class NewFileDialog: Control {
   controls: {
       "Ok button": {$ ('# NewFileOkButton')}
   }
 }

 class OpenFileDialog: Control {
   controls: {
       "Ok button": {$ ('# OpenFileOkButton')}
   }
 }

 class Root: Control {
   controls: {
       "New File dialog box": {$ ('# NewFileDialog')}
       "Open File dialog box": {$ ('# NewFileDialog')}
   }
 }

Resolver template


Symptom: Rezolving of entities by their string representations (parameters of implementation of steps) is carried out within the methods of implementation of steps.

Remedy : Put the code for resolving entities by name into the common code by providing automatic translation to the final types.

Result: Shortening and unifying the implementation of steps, including all checks. Increased fail-safe execution of steps.

Pattern Application:

 Scenario outline: File / New menu item
   Given Trader Application is started
   When user clicks on 5th menu item
   Then Save As dialog box should be displayed 

 Groovy part:
 When (~ 'user clicks on (. +)') {Control control ->
   control.displayed.shouldBecome (true)
   control.click ()
 }

 Registration part:

 class NewFileDialog: Control {
   controls: {
       (~ "(. +) menu item"): {Integer index -> $ ('. Menu'). at (index)}
   }
 }

 For example, autoconversion String-> Integer allows, for example, to record the positions of elements with the text:
  - 1st menu item / first menu item
  - 2nd menu item / second menu item
  - 3rd menu item / third menu item
  - #th menu item

Readability Template


Symptom: The script code is difficult to read due to the use of program-related values ​​(numbers (“1”) instead of words (“first”)).

Solution: Implement automatic conversion from a format that is understandable to a person into internal formats, with which it is convenient to work with automatic input validation.

Result: Unified Script Recording. Scenarios are more understandable to business users, outsiders, and new team members.

Pattern Application:

 Conversions:
  - should / should not / should not -> Boolean
  - can / cant -> Boolean
  - 0,1,2,3 / 1st, 2nd, 3rd, 4th / first, second, third, fourth

Names Postfix Template


Symptom: The script code does not define the entity types in the entity names, making it difficult to understand what is being done.

Solution: Append to the names of entities when they are registered their type (button / checkbox / message box / ...).

Result: Instant understanding of the types of elements, the composition of the elements, over which the action on the cursory glance on the scenario.

Pattern Application:

  - Ok button
  - Save button
  - New File message box
  - No Way radio button

Variable pattern


Symptom: Scenario code depends on external factors that must be used in both actions and checks.

Solution: Creating variables that store the values ​​of external factors.

Result: Parameterized scripts that contain custom values ​​or values ​​that are not directly dependent on the script.

Pattern Application:

 Transformations String-> String
  - '{{today}}' -> '27 Jan 2016 '// dynamically calculated
  - '{{yesterday}}' -> '26 Jan 2016 '// dynamically calculated
  - '{{today + 7}}' -> '03 Feb 2016 '// dynamically calculated
  - '{{user.email}}' - 'FooFoo@mail.com' // is stored separately in test.config, can be changed by parameter in TeamCity
  - '{{user.password}}' - 't0psecret' // is stored separately in test.config, can be changed by parameter in TeamCity

  And user enters '{{user.email}}' into Login field
  And user enters '{{user.password}}' into Password field

Further work


In the future we plan to expand the design patterns of Cucumber BDD code by expanding this article and publishing supplements to it. Of course, comments and additions are accepted in the form of your design patterns.

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


All Articles