Behaviour-Driven Development has gained some attention over the last few years with some companies embracing this methodology. Others are more sceptical and may see additional “code” as waste. Let’s take a closer look.
Definitions
Before we weigh up the pros and cons of applying the BDD approach let’s define a few concepts that may appear to be confusing at first.
What is BDD?
Behaviour-Driven Development, or BDD, is a software development methodology which aims to bridge the gaps between product owners and engineers, and between engineers and testers.
It allows the person that has been tasked with writing User Stories to come up with business scenarios that best describe the desired Behaviour of a given system.
From that point on those scenarios can become Living Documentation that anyone can look up when in doubt about what a given piece of software is meant to do.
Those scenarios can, and in fact, should be used as the base for automated testing of that system which,in turn, becomes the System Under Test.
Gherkin syntax
Photo: Canned pickle cucumbers by Marco Verch under Creative Commons 2.0
BDD features and scenarios are written in their own text file format called Gherkin.
These files are human-readable and can help any party understand how a given system is meant to work.
See the example of an add-relative.feature
file below:
@StableFeatures
Feature: Adding relatives to a family tree
Background:
Given I navigated to Findmypast
And I am logged in
Scenario: Adding a child to a family tree
Given I am looking at my family tree
When I click on a family member
Then The person drawer appears with the selected person's details
When I click on the Add relative button
Then The relative selection dialog appears
When I click on the "Child" option
And I click the Continue button
Then The person details dialog appears
When I fill in the new relative details
And I click the Continue button
Then The new relative appears in my tree
The Feature
describes the high level summary of what a given system “can do”.
A Feature
can have multiple scenarios defined.
These can include “happy” or “golden” paths but also “sad” paths (e.g. “The password provided is incorrect”).
Even those that cover past fixed bug edge cases.
Scenarios in a given feature can have common setup tasks.
These are Steps defined under Background
.
Every scenario defined in this feature file will start with these Steps.
Essentially every action or assertion that forms a scenario can be described as a Step.
Steps have to start with one of the following keywords: Given
, When
, Then
, And
.
The form the basis of any testing scenario for any computer system.
The Given-When-Then
Steps echo the familiar triple-A sequence from other testing domains (AAA
= Arrange
, Act
, Assert
).
In many pieces of modern-day unit testing code you can find comments above blocks of code as such:
// Arrange
const subject = new StringConcatenator()
const testStrings = ['apples', 'bananas', 'carrots'];
const linker = '_';
// Act
const result = subject.concatenate(testStrings, linker);
// Assert
expect(result).to.equal('apples_bananas_carrots');
The BDD Given-When-Then
setup takes it to the next level and applies the same logic to the wider system.
What is Cucumber then??
Photo: Cucumbers by Marco Verch under Creative Commons 2.0
BDD scenarios are written in the plain text file Gherkin syntax.
In the Node / JavaScript world these scenarios are used in test automation by using tools such as Cucumber.js. This tool allows you to write programmer-readable code that will be executed in any environment that has the relevant dependencies installed.
Browser testing frameworks such as Selenium or Cypress can make use of the BDD setup:
- WebdriverIO
- BrowserStack Test Automation using Selenium and Cucumber (Java)
- Cypress Automated Testing using Cucumber
How is test automation wired together with all this?
In order for a plain text file to be understood by Node (or any other programming language supporting this for that matter) you need to match up those step lines with Step Definitions. These are actual JavaScript expressions that capture the plain-language strings and execute the code that tests the system.
To use the example from the previous paragraph:
// The "Arrange" step in BDD fashion
Given('The test strings and linker are set up', () => {
this.context.subject = new StringConcatenator();
this.context.testStrings = ["apples", "bananas", "carrots"];
this.context.linker = "_";
});
// The "Act" step in BDD fashion
When('I call the string concatenator with the strings and linker', () => {
this.context.result = this.context.subject.concatenate(
this.context.testStrings,
this.context.linker
);
});
// The "Assert" step in BDD fashion
Then('The resulting string should match the expected concatenated one', () => {
expect(this.context.result).to.equal("apples_bananas_carrots");
});
The Cucumber Browser Demo allows you to prototype valid BDD scenarios:
The context
is the “dumping ground” that you can use between steps to store data and results.
This object is usually set up outside of the Step Definitions in what Cucumber defines as the “world” or in its own file, e.g. support/world.js
:
class CustomWorld {
constructor() {
this.context = {};
}
}
setWorldConstructor(CustomWorld);
Test results:
The files for the demo are available here:
What is living documentation?
The BDD feature files can be used to generate living documentation which allows every stakeholder to see what a given system is meant to be doing.
At Findmypast we are actively writing our scenarios so that they can be digested by our document store and presented as such:
When setting up a Living Documentation library it is good to wire it up with the automated testing your CI/CD system of choice. This will allow any Agile team to effectively know WHAT a given system is meant to be doing and WHETHER or not it’s doing it right now.
Use case
BDD can bridge gaps between Agile development teams, stakeholders and product owners. It provides a common language in which these parties can talk about the systems they are releasing.
Documenting features and bugs
The BDD syntax can be the start of a new feature of a system.
Aside from the usual Acceptance Criteria in a user story you should set up the expected feature with one or more BDD scenarios.
This *.feature
file might not be used for anything else but it will document what a given system is meant to do.
It is also a good idea to collect the existing business *.feature
files into a document store.
From there any stakeholder will be able to determine what the acceptance criteria currently are or were in the past.
Preventing bugs by running automated tests
The *.feature
files are the basis for writing automated tests.
As you develop the software and flush out technical problems your scenarios might need to change to reflect additional steps.
Sometimes you may find that steps defined in a scenario make no functional sense.
However you eventually end up with a Feature with multiple Scenarios that are granular enough.
The scenario steps should run as test regression and provide answers as to whether the System Under Test is working as it should, ideally in a continuous integration/testing/deployment setting.
Sample BDD Feature
In my past side project called Cucumber Boilerplate I have outlined how a tiny Online Shopper Express Web application can be tested automatically using a set of BDD scenarios:
Feature: Online Shopper payments and available items
Background: Always open the payment page first
# "Arrange"
Given The "payment-page-id" page is opened
Scenario: Verifying that the page welcome header contains the expected text
# "Act"
When I get the text value of the page welcome header
# "Assert"
Then The expected header text value equals "Welcome to Online Shopper - Payment Page"
Scenario: Clicking on the show payment details button to reveal them
When I click to show payment details button
Then The payment details are revealed
Scenario: Clicking the Pay for Items button inside the payment frame shows the payment time
When I click on the Pay for Items button
Then The payment time text appears with a new value
When I click on the Pay for Items button
Then The payment time text appears with a new value
Scenario: Verifying that the page welcome header is different in the Available Items page
When I get the text value of the page welcome header
Then The expected header text value equals "Welcome to Online Shopper - Payment Page"
When I click the "Available Items" top navigation link
And I get the text value of the page welcome header
Then The expected header text value equals "Welcome to Online Shopper - Available Items"
When I click the "Payment Page" top navigation link
And I get the text value of the page welcome header
Then The expected header text value equals "Welcome to Online Shopper - Payment Page"
The contents of the features directory contains all the necessary Gherkin and JavaScript code to run those test scenarios.
This project uses a combination of WebdriverIO, Selenium and Cucumber to interact with the browser and the sample website but the test framework is BDD and its scenarios are listed above.
The previously mentioned “world” is set up in world.js.
This defines the context
for sharing context between the step definitions:
When(/^I get the text value of the page welcome header$/, async function () {
const page = this.context.page;
this.context.welcomeHeaderText = await page.getWelcomeHeaderText();
});
Then(/^The expected header text value equals "([^"]*)"$/, function (expectedValue) {
const welcomeHeaderText = this.context.welcomeHeaderText;
should.exist(welcomeHeaderText);
welcomeHeaderText.should.equal(expectedValue);
});
For comparison there is also “traditional” testing code using mocha. These tests do exactly the same thing but with Mocha you don’t get a text file that everyone can understand and determine what the particular website is capable of.
Conclusion
As with any “additional” software-building methodology there are time and effort costs involved. You need to set up the Gherkin feature files and may need to change your automated tests so that step definitions are clear.
However once you have everything in place then adding, modifying and removing redundant scenarios becomes pretty easy.
The great things about BDD scenarios is that once you have build up a big enough step base then constructing new testing scenarios requires less work because of easy step re-use.
About the Author
My name is Miroslaw “Mirek” Majka and I’m a Senior Software Engineer at Findmypast. I enjoy building and testing Web solutions of every size and proportion. I have build end-to-end testing solutions using C#, Node.js, Postman, Selenium and Cypress for various companies in the past. I believe that with BDD you can have more people in your company invested in delivering quality software than just engineers.
If you’d like to join our fantastic team of engineers visit the Findmypast Careers page!