Writing automated unit tests for the software we build can seem like a large amount of groundwork without a clear payoff. However, the long-term benefit to your team’s health, happiness, and velocity dwarfs the upfront investment. Among many other benefits, automated testing catches bugs, enables continuous deployment, and makes it easier for developers to chip in on unfamiliar code.
Cucumber is a tool that allows us to create automated software tests in an easy to write, easy to read way. Although many product teams struggle to get automated testing off the ground (perhaps never having heard of tools like Cucumber), nearly all of them have unwittingly dabbled with one of its key components — Gherkin.
When used judiciously, writing acceptance criteria in Gherkin form is a great way for teams to define and agree on what ‘done’ means for the features they build. Despite its simplicity, Gherkin is a very nuanced language. With that nuance comes a lot of confusion about what separates a statement that is written well from one that is written poorly, especially for people new to Gherkin.
In isolation, Gherkin’s quirks can seem confusing, but when viewed in the context of automated Cucumber tests, those subtleties make a lot more sense. This article goes over how Gherkin and Cucumber fit together so that you can see how to use Gherkin more effectively today. By the end of this article, you will learn how a full Gherkin + Cucumber implementation works and how building a library of well written Gherkin scenarios paves the way for automated testing.
Gherkin
Gherkin is a Domain Specific Language for writing acceptance criteria that has five main statements:
Scenario
— a label for the behavior you’re going to describeGiven
— the beginning state of the scenarioWhen
— a specific action that the user takesThen
— a testable outcome, usually caused by the action inWhen
And
— this continues any of the other three operators
Together these statements describe all of the actions that a user must take to perform a task and the result of those actions.
For example, a real-world Gherkin example might look like this:
Scenario: User clicks the link
Given I am on the homepage
When I click the provided link
Then I should see the link click confirmation
Or to generalize, like this:
Scenario: Some determinable business situation
Given some precondition
And some other precondition
When some action by the actor
And some other action
And yet another action
Then some testable outcome is achieved
And something else we can check happens too
Generally speaking, the Product Manager writes Gherkin acceptance criteria before the team starts work on a feature. Having acceptance criteria outlined in an easy to read, easy to understand structure makes it a lot easier for developers and designers to understand the intended user flow. This is valuable in its own right, but this is only a small part of the value of well structured Gherkin. After all, there are many effective ways of writing feature descriptions (such as: As a ___ I want to ___ so that ___
) that can make communication easier.
These other approaches have their place and in many cases are compatible with Gherkin. However, once we graduate beyond manual QA and into automated testing, the real value of Gherkin comes to the forefront. With a few clever techniques, the English Gherkin scenarios we write can be automatically translated into testable automated code. To see what I mean, let’s take a quick look at Gherkin’s big brother --- Cucumber.
Cucumber
Regular Expressions
Translating Gherkin scenarios to code uses a technology called Regular Expressions, or as it’s commonly referred to, Regex.
The problem that Regex solves is finding specific words, phrases, or characters within a body of text. To accomplish this goal, the creators of Regex defined a set of characters and symbols that represent the logic behind matching parts of the text. For example, $
is used to mark the end of a string. As such, the regular expression fox$
would match silver fox
(because the last three letters are f-o-x
) but not foxy
(because the last letter is y
).
Cucumber uses Regex to scan the scenarios that we define for Gherkin’s keywords (Scenario
, Given
, When
, Then
, and And
) and the phrases that follow them. When Cucumber finds a phrase that it recognizes in one of our scenarios using Regex, it translates that phrase into code by using something called Step Definitions.
Step Definitions
Step definition files are like a foreign language dictionary. They give our test suite a way to translate English scenario steps into code that we can run. On most teams, the developers who will build the feature write the step definitions.
Step definition files look something like this:
When(/^I go to the homepage$/) do
visit root_path
end
Then(/^I should see the welcome message$/) do
expect(page).to have_content(“Hello Cucumber”)
end
The first statement is saying “any time you find the string ’I go to the homepage’ then run the visit root_path
function”.
The second statement is saying “any time you find the string ‘I should see the welcome message’ then we expect the current page to have the text ‘Hello Cucumber’ somewhere on it”.
Each step in a scenario should have a step definition so that the automated test suite knows how to translate our English into code. Over time you will write hundreds of these step definitions, many of which are reusable across your test suite (such as the first step defined above).
Caveat: Reusing functions is only possible if we use a single phrase for a single action. If our scenarios use both I go to the homepage
and I visit the homepage
interchangeably, then we have to maintain two separate step definitions for the same action. This duplicated code makes our test suite harder to maintain and violates the DRY (don’t repeat yourself) principle of software development.
As our applications and our test suites grow our step definitions and our number of scenarios grow alongside them. This can quickly get messy and become overwhelming. To help organize our test suites, we use feature files.
Feature Files
To help manage the complexity of a big test suite, we group together our Gherkin scenarios into feature files. Feature files are like a virtual checklist for making sure that your software works. In addition to the scenarios needed to test a feature, Feature files also have a short description of the feature and any business rules or additional information that helps to describe what the feature does.
A feature file might look something like this:
Feature: Some terse yet descriptive text of what is desired\
Textual description of the business value of this feature\
Business rules that govern the scope of the feature\
Any additional information that will make the feature easier to understand
Scenario: Some determinable business situation\
Given some precondition\
And some other precondition\
When some action by the actor\
And some other action\
And yet another action\
Then some testable outcome is achieved\
And something else we can check happens too Scenario: A different situation\
...
An application can (and should) have many feature files to describe how your features work. These tests work together to give you an overview of your application’s health.
Output
When you run your test suite on the command line using Cucumber you will get an output something like this:
$ cucumber -s
Using the default profile...
Feature: Hello CucumberScenario: User sees the welcome message
When I go to the login page
Then I should see the welcome message1 scenario (1 passed)
2 steps (2 passed)
0m0.148s
Or for something more tangible:
$ cucumber -s
Using the default profile...
Feature: Google Homepage SearchScenario: User sees the header
Given I'm on the homepage
Then I see the headerScenario: User can search with "Google Search"
Given I'm on the homepage
When I type "random page" into the search field
And I click the Google Search button
Then I go to the random page search resultsScenario: User can search with "I'm Feeling Lucky"
Given I'm on the homepage
When I type "random page" into the search field
And I click the I'm Feeling Lucky button
Then I go to a random pageScenario: User can see the Terms and Conditions
...15 scenarios (15 passed)
87 Steps (87 passed)
0m3.141s
Taking a look at the output from running your test suite gives you an immediate way of checking in on your application. It can show where your changes broke a piece of old functionality or where new functionality isn’t working as designed. This makes it easy to pin down and fix those issues so that you can ship with confidence.
Wrapping Up
Using Gherkin to write scenarios makes it simple to show the flows that the product needs to have. Feature files help organize those flows into logical chunks. Cucumber scans those chunks and gives us a live readout of which pieces of our software work and which don’t.
A full test suite where every feature has a feature file and set of scenarios is a powerful tool. It makes it easy to catch bugs in new features, to avoid introducing bugs in old features, and to communicate progress with stakeholders. Long story short, it automates some of the biggest delays between building a feature and getting it into your users’ hands.
Using Gherkin and Cucumber isn’t the only way to build automated acceptance criteria testing, but it is a structure that’s easy to scale up. You can start writing Gherkin immediately and as your team matures and grows, start adding in step files and automated testing with Cucumber. When they’re used effectively, Cucumber and Gherkin give us a clear (and iterative!) path towards full and automated unit testing.
Examples borrowed from Cucumber, Conductor, and SemaphoreCI.