TestQuality Blog

Cucumber and Gherkin Language Best Practices

Cucumber and Gherkin Language Best Practices | TestQuality
BDD or Behavior Driven Development is a development approach, but even if you don't utilize it, we think Cucumber (or a comparable tool) is useful since it "pushes" you to document your automated tests before implementing them.
Cucumber is another traditional Selenium substitute, but there are some significant differences between the two systems. Cucumber is an automation tool for behavior-driven development (BDD), whereas Selenium is an automation tool for web apps.
It is critical that we make these tests accessible and simple for a user who does not already understand the behavior of the feature presented, as well as more maintainable to reduce the costs of making changes to the test steps. This graphic represents the concept of merging automated testing, having live documentation, and having executable specs. All of this is due to the technique of employing a Cucumber-style tool.



To work with Cucumber, you will need the following files:

Feature file, text file containing the acceptance criteria in Gherkin format. These acceptance criteria might be viewed as the tests that we will create.
Step Definitions are files in the programming language used where Cucumber may associate which actions to perform connected with each step of each acceptance criterion described in the various features.
Then, depending on the amount of testing, more files may be required. For example, if we are doing this at the level of the presentation layer, of the graphical Web interface, we will use something like Selenium, for which a design pattern such as Page Object is recommended. But this is already more detailed and depending on what we are testing, and we want to focus on the Cucumber component in this post.

Feature files (scenarios in Gherkin)

Creating a feature

In a previous post we explained What Is Gherkin and How Do You Write Gherkin Tests?To begin, we establish a folder in the project to contain the Gherkin features that we will be writing. In this example, we'll use a BDD practice example to model the behavior of a cashier using Gherkin functions, and we'll do it utilizing these methods.


Suppose we are interested in modeling the behavior of an ATM when we want to withdraw money:

Scenario: As [specific user]
I want [perform concrete action]
for [result or benefit]

The important thing is that you briefly explain what you want to achieve with the scenario.
One way to start writing our Feature can be this:

Feature: Money withdraw

Scenario: As an existing and enabled user of the ATM, I want to make a withdrawal to get money.


Some key features concerning Feature files:


  • It is ideal to use a Feature for each system functionality, and make sure that what is specified in the Feature is distinct to that feature and as independent from other functions as possible.
  • The ideal method to make our Feature files comprehensible for a client is to use the same language that the client uses to describe the functionality, therefore it is always better to describe the actions in the same way that the client would.

Features and scenarios

In Gherkin language, the scenarios are examples of individual behavior to establish acceptance criteria, so we may be interested in writing several per functionality to observe different results and make our test more complete (it is recommended to write the positive scenarios first).


Gherkin statements are used to describe the scenarios: Given, When, Then, But and And.

The most important aspect is that the steps define what you want to achieve in the functionality rather than how you want to execute it (this is the responsibility of the step definitions, explained below). This is an example of a poorly constructed scenario:


Scenario: As an existing and enabled user of the ATM,

I want to make a withdrawal to get money.


Given I authenticated with an enabled card

And The available balance in my account is positive

And the teller has enough money

And The cashier has enough paper to print receipts.

When I insert the card in the ATM

And I write on the keyboard the pin of the card

And I press the confirm pin button

And I press the button next to the withdraw money option

And I deposit an amount less than or equal to my available balance

And I press the confirm extraction button

And I press the button to confirm print receipt

.......


It is advisable to avoid writing scenarios in this manner since it lengthens them, adds superfluous complexity, and makes them harder to read and understand. Another problem of creating them this way is that they are difficult to maintain since even minor changes to the behavior will almost certainly need us to adjust numerous steps in the scenario. This might be a more concise, detailed, and straightforward method of expressing the scenario:

Scenario: As an existing and enabled user of the ATM,
I want to make a withdrawal to get money.

Given I authenticated with an enabled card
And The available balance in my account is positive
When I select the option to withdraw money
And I enter the amount of money less than the available and available balance of the cashier
Then I get money
And The money I got is subtracted from the available balance of my account
And The system returns the card automatically
And The system displays the transaction completed message

Here are some key elements concerning Gherkin stages and steps:

  • Given-When-Then statements must be written in this sequence. This is due to the fact that Given denotes a precondition, When an action, and Then an outcome or consequence of the action (user acceptance criteria). Writing a When after a Then, for example, would be theoretically incorrect and illegible.
  • To expand any of the assertions, the Given-When-Thens should not be repeated on each scenario. This is because a scenario describes an individual's behavior, and if we specify something like: Given-When-Then-When..., we can certainly break it into more than one scenario.
  • The sentences must be consistent with one another and with the scenario description; for example, if the scenario description is written in the first person, the sentences must likewise be written in the first person (we will develop the question of the grammatical persons that are used in the writing of the scenarios in the next section of the article).
  • The goal is to employ as few steps as feasible for a single scenario, such that a user who is unfamiliar with the capability may grasp it by reading the scenario. The less you have to read in order to comprehend it, the better.
  • It is also suggested that the sentences be explanatory and brief.
  • We should write the scenarios in the order in which we would like to be presented with them.
  • The But statement works similarly to the Then statement, but it is intended to ensure that a given result is not seen, for example:

Given I fulfill a precondition
When I execute an action
Then I see this result
But you should not be able to observe this other result

  • It is critical that the scenarios be as independent of each other as possible; that is, scenarios cannot be connected. For example, it is inconvenient that if we enter entries in a database in one scenario, the outcome of succeeding scenarios is dependent on the availability of those records. Coupled scenarios might cause issues, for example, if they must be executed in concurrently or if one fails.

How should the files be separated?
Because separating the features might result in a large number of files, you must consider how to separate the features into various files.

A popular solution is to have a file with features that collect everything relevant to a specific part of the program and even organize it into directories.

For example, in the case of a show system, we may have:

We may have a "shows" folder on the first level.
Inside are several features for creating, editing, deleting, and managing them.
It is better arranged this manner, and it is simpler to discover each test and where to find each thing.

Scenario: As an existing and enabled user of the ATM,
I want to make a withdrawal to get money.

Given I authenticated with an enabled card
And The available balance in my account is $10,000
And The teller has $100,000 in cash
When I select the option to withdraw money
And I indicate that I want to withdraw $1,000
Then I get $1,000 in two $500 bills
And My account balance becomes $9,000
And The teller keeps $99,000 in cash
And The system returns the card automatically
And The system displays the transaction completed message

Is it better to write in the first or third person?
The point of view that should be employed is a frequently asked question while constructing a scenario (and its associated steps). The common question is whether I should write the scenarios in the first or third person. The problem is more complicated than it appears.

Because the examples in the official Cucumber manual incorporate both perspectives, it is not an exact reference for solving the problem.

Let us examine the reasoning for each of the views.

First-person pronoun use
According to a Stack Overflow reference, Dan North (considered as the originator of BDD) suggests using the first person, and it is the one he uses to compose his scenarios in his Introducing BDD post.

The usage of the first person allows you to create the scenario in a way that is consistent with its description, which, as previously said, generally takes the form "As [a specific user], I want [to execute a certain action] for [result or benefit]."

Given that the description specifies the specific role or user for which the scenario is built, and the goal is to put oneself in the shoes of that user, the usage of the first person is appropriate.

Utilization of the third person
Proponents of this viewpoint claim that using the first person allows the reader of the scenario to lose track of the role or user being described. Who deletes if I put in a step, "I delete an item from the system" An administrator, or a specific user? A set of roles?

In certain ways, using the third person minimizes the reader's risk or difficulty in making incorrect assumptions about who the stakeholder(s) engaged are.

It is also stated that using the third person, particularly in English, conveys information in a more official and impartial manner.

Conclusion
When developing scenarios, there is no clear rule on which point of view to utilize. The crucial thing at this stage, as previously said, is to preserve consistency between the description of the scenario and its steps (do not switch grammatical persons), follow the criteria used if we are adding scenarios to an existing project, and favor clarity of what is written.

It is also stated that using the third person, particularly in English, conveys information in a more official and impartial manner.

Other terms used to describe scenarios
Background
If particular preconditions are satisfied in all scenarios of the same feature, using a Background is far more practical than writing the same thing numerous times. This is a set of steps that must be performed before all scenarios in the feature. Consider the following example:

Feature: Money extraction

Background:
Given The credit card is enabled
And The available balance in my account is positive
And the teller has enough money

Scenario:

It is advised that the Backgrounds be as brief as possible in terms of the number of steps, since if they are too long, the scenarios that follow the Background may be difficult to understand. It is usually preferable to have scenarios that are as self-contained as feasible, and to have a Background that is as brief as possible.

Outline of the Scenario
Scenario Outlines are a form of scenario that specifies the input data. They are particularly useful since they eliminate the need to develop a scenario for each input data set, such as:

Scenario outline: Withdraw money with different card codes.

Given The credit card is enabled
And The available balance in my account is positive
And the teller has enough money
When I insert the card in the ATM
And Enter the <pin> of the card

Examples:
| pin |
| 5689 |
| 2358 |

Doc Strings
Doc Strings may be used to add long character strings to a step in a more descriptive manner.

To utilize them, you must insert the necessary content between three double quotations ("""). As an example:

Scenario outline: ...
Given...
When...
Then I get money
And A confirmation message is displayed with the text:
"""
Dear Customer:
The following amount has been withdrawn from your account no. <account>: <amount>.
Thank you for using our services.
"""
Examples:
|   pin   |     account     | amount |
| 5689 | 145823891 |  4000  |
| 2358 | 985612374 |  7000  |

As seen in the above example, a Doc String (which is an input data in and of itself) can be used in conjunction with other input data to display data particular to the scenario being performed.

Data Tables
Scenario Outlines' structure and function are quite similar to Data Tables'. Their primary distinctions are as follows:

The Data Tables are defined at the step level rather than the scenario level, therefore they are used to provide input data to a particular step inside the scenario.
It is not required to establish a header for them, however it is beneficial and suggested in order to conveniently reference the data.
An example of its application:

Scenario: Withdraw money with different card codes.
Given The credit card is enabled
And The available balance in my account is positive
And the teller has enough money
When I insert the card in the ATM
And I enter the following <pin> and get the result <result> :
| pin | result |
| 5689 | Bug |
| 2358 | okay |

In the above example, we added a second column called "result" to reflect the predicted result based on the inputted PIN ("5689" is incorrect, while "2358" is right). The Data Tables are not required to be utilized in this manner, but it is supplied as an example of how the input data might be used in a scenario.

Languages
Cucumber allows you to build scenarios in multiple languages while adhering to the same principles that we use in English.

To utilize this feature, the functionality must begin with "# language:", followed by the dialect code (for example, "# language: es" for Spanish or "# language: fr" for French ).

In the official Cucumber documentation, you can find all the information you need to use this feature, including the code for each dialect and the words that should be used to replace the usual ones (for example, in the Spanish language, "Characteristic" is used instead of "feature", "Scenario" instead of "scenario", and so on).

Tags
On certain occasions it may happen that we do not want to execute all the scenarios of our test and instead we want to group certain scenarios and execute them separately. Cucumber provides a way to configure this using tags. Tags are annotations used to group and organize scenarios and even features, these are written with the @ symbol followed by a significant text, examples:

@gui
Features: …

@SmokeTest @wip
Scenario: …

@RegressionTest
Scenario: ...

It is important to note that the tags that we specify to the titles of the Feature files will be inherited by the scenarios of the same, including Scenario Outlines.

If we have a Scenario outline under a tag, all the data examples that the scenario has will be executed under that tag.

Having assigned our tags, there are many ways to configure them at runtime in the tags section of @CucumberOptions. Some examples:

tags = {“@SmokeTest”} Execute all scenarios under the @SmokeTest tag

tags = {“@gui”} Executes all the scenarios under the @gui tag, as in the example we have a feature under this tag, all the scenarios of that feature will be executed.

tags = {“@SmokeTest, @wip”} Executes all the scenarios that are under the @SmokeTest tag or under the @wip tag (OR condition).

tags = {“@SmokeTest”, “@RegressionTest”} Runs all scenarios under the @SmokeTest and @RegressionTest tags (AND condition).

tags = {“~@SmokeTest”} ignore all scenarios under the @SmokeTest tag

tags = {“@RegressionTest, ~@SmokeTest”} executes all scenarios under the @RegressionTest tag, but ignores all scenarios under the @SmokeTest tag

tags = {“@gui“, “~@SmokeTest”, “~@RegressionTest”} ignores all the scenarios under the @SmokeTest and @RegressionTest tag but executes all the ones under the “@gui” tag, if we follow the example it is like executing all the scenarios of the feature that are not under any other tag.

In brief, tags are important not just for organizing and categorizing our scenarios/features (which improves test clarity), but also for executing them selectively, such as running the quickest scenarios more frequently.

Definitions of Steps (implementation of the steps)

We already specified the steps of our scenarios and outlined the procedures that our test would follow, but we did not specify how we want it to be done. This is the implementation of the Gherkin statements that we write in the scenarios (step definitions).

Cucumber uses the step definitions to translate the steps we define into actions to execute in order to interact with the system.

Although the examples for implementing the steps provided here are written in Java, it should be noted that Cucumber may also be used with JavaScript, Ruby, C++, and other languages.

If we are working in an IDE that already has Gherkin and Cucumber dependencies installed, it will recommend implementing them (they will show underlined), and it will let us to create a.java file or pick one that already has steps implemented. Choosing one of these two options will result in the creation of a method in the class, such as if we choose to construct a step definition for the step:

Given The credit card is enabled

We will automatically build a method with an annotation, with the header text matching the step description:

@Given("^The credit card is enabled$")
public void checkCardEnabled() throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}

In the event that the step includes input data defined through the Scenario Outline or Data Tables, these data are included in the annotation as regular expressions, and in the method as received parameters:

@When("^I enter the \"([0-9]+)\" of the card $")
public void enterPIN(int pin) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}

When we do this, the feature step (the Gherkin statement) automatically knows where the implementation is. Here are some key factors to remember while establishing step definitions:

  • The ideal solution is to construct step definitions that only need to be implemented once and can be utilized in other scenarios (even from different features).
  • This is useful not only because it reduces the amount of code that must be written, but it also adds significantly to the maintainability of the tests because the number of step definitions that must be modified will be reduced in any scenario.
  • Step definitions can be reused by defining them in Scenario outlines and parameterizing them.

TestQuality and Gherkin

TestQuality's import capabilities also allow you to import requirements, tests, and issues by uploading Gherkin Feature Files easily with an import data option menu even when using a Gherkin based Test results JSON file.

Gherkin feature files can be uploaded via TestQuality REST interface via curl, a popular command line tool.

Once your file has been added, you can optionally choose a Cycle and Milestone that you would like to link to your Test Run result.

Gherkin Feature Files to import Test Cases to TestQuality

Gherkin Feature Files with your tests can be easily imported with TestQuality


TestQuality is designed around a live integration core that allows TQ to communicate directly with GitHub and integrate with Jira in real-time linking issues and requirements with the key tools in your DevOps workflows.



TestQuality's integration engine also allows you to connect to pull in automated test results from popular CI/CD, Test Automation, and Unit Testing systems. As we have previously described, TestQuality easily imports Gherkin Feature Files and it has all the test management capabilities you need for creating, maintaining, organizing, and running tests, but TestQuality is different from other test management tools in that it is purpose built for GitHub and Jira workflows and designed to be integrated with virtually all test automation and unit testing tools.

Join now and Try TestQuality for Free!