Skip to main content

BDD with Gherkin and Cucumber

What is BDD (Behavior Driven Development)?​

BDD (Behavior Driven Development) is a development methodology that focuses on the "behavior" of software. It is characterized by developers, QA, and business stakeholders describing requirements in a common language, and these specifications can be executed as test code.

Main Purposes of BDD​

  • Establish Common Language: Technicians and non-technicians communicate in the same language
  • Documentation of Specifications: Executable tests also function as specifications
  • Clarification of Requirements: Clarify "what should be built"
  • Living Documentation: Code and specifications are always synchronized

Difference from TDD (Test Driven Development)​

ItemTDDBDD
FocusCorrectness of codeBehavior of the system
WriterMainly developersDevelopers + QA + Business side
Test GranularityUnit test (Function/Method)Scenario test (Function/Feature)
Description LanguageProgramming languageNatural language (Gherkin etc.)
Test PurposeQuality assurance of codeVerification of requirement realization
DocumentationLow (For technicians)High (Readable by anyone)
Development ProcessRed -> Green -> RefactorSpec description -> Implementation -> Verification

Relationship between TDD and BDD​

  • TDD: "Building the thing right"
  • BDD: "Building the right thing"

BDD and TDD are not conflicting but complementary. By writing requirement-level tests with BDD and implementation-level tests with TDD, comprehensive quality assurance becomes possible.

What is Gherkin?​

Gherkin is a natural language-based description language used in BDD. It describes software behavior in a syntax that can be read and written even by people without a technical background.

Basic Syntax of Gherkin​

Gherkin consists of the following keywords:

  • Feature: Description of the function
  • Scenario: Specific scenario
  • Given: Preconditions
  • When: Action to execute
  • Then: Expected result
  • And / But: Additional conditions or actions

Gherkin Example​

Feature: User Login Function
Verify that users can log in to the system

Scenario: Login success with correct credentials
Given User "tanaka@example.com" is registered with password "password123"
When User attempts to login with email "tanaka@example.com" and password "password123"
Then Login is successful
And User is redirected to the dashboard screen
And Message "Welcome, Mr. Tanaka" is displayed

Scenario: Login failure with incorrect password
Given User "tanaka@example.com" is registered with password "password123"
When User attempts to login with email "tanaka@example.com" and password "wrongpass"
Then Login fails
And Error message "Email or password is incorrect" is displayed

Scenario: Login failure with non-existent user
Given Email "unknown@example.com" is not registered in the system
When User attempts to login with email "unknown@example.com" and password "anypass"
Then Login fails
And Error message "Email or password is incorrect" is displayed

Scenario Outline (Parameterization)​

If you want to test with multiple data patterns, use Scenario Outline and Examples:

Feature: Shopping Cart Calculation

Scenario Outline: Calculation of total amount according to item quantity
Given Shopping cart is empty
When Add <quantity> items with unit price <unit_price> yen
Then Total amount of cart becomes <total> yen

Examples:
| unit_price | quantity | total |
| 100 | 1 | 100 |
| 100 | 5 | 500 |
| 250 | 3 | 750 |
| 1000 | 10 | 10000 |

What is Cucumber?​

Cucumber is a BDD framework that converts scenarios written in Gherkin into executable tests. It supports various programming languages.

Supported Languages​

  • Cucumber-JVM: Java, Kotlin, Scala
  • Cucumber-Ruby: Ruby
  • Cucumber-JS: JavaScript, TypeScript
  • SpecFlow: C# (.NET) *Equivalent to Cucumber
  • Behave: Python *Equivalent to Cucumber

Cucumber Workflow​

  1. Create Feature File: Describe Gherkin scenarios with .feature extension
  2. Create Step Definitions: Write implementation code corresponding to each step
  3. Execute Test: Cucumber reads Feature file and executes Step Definitions
  4. Generate Report: Output test results in HTML, JSON, XML, etc.
┌─────────────────┐
│ Feature File │
│ (Gherkin) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Cucumber Runner │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Step Definitions│
│ (Impl Code) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Application │
│ (Test Target) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Test Report │
└─────────────────┘

Implementation Examples​

JavaScript/TypeScript​

1. Project Setup​

npm install --save-dev @cucumber/cucumber
npm install --save-dev @types/node typescript ts-node

2. Create Feature File​

features/login.feature

Feature: User Login Function

Scenario: Login with valid credentials
Given User accesses the login page
When Enters email address "user@example.com"
And Enters password "securepass"
And Clicks login button
Then Dashboard page is displayed
And Welcome message is displayed

3. Implement Step Definitions​

features/step_definitions/login.steps.ts

import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from 'chai';

// Test target class (example)
class LoginPage {
private email: string = '';
private password: string = '';
private isLoggedIn: boolean = false;

navigateTo() {
// Implementation of page transition
}

enterEmail(email: string) {
this.email = email;
}

enterPassword(password: string) {
this.password = password;
}

clickLogin() {
// Actual authentication logic
if (this.email === 'user@example.com' && this.password === 'securepass') {
this.isLoggedIn = true;
}
}

isDashboardDisplayed(): boolean {
return this.isLoggedIn;
}

getWelcomeMessage(): string {
return this.isLoggedIn ? 'Welcome' : '';
}
}

// Definition of World (Context)
let loginPage: LoginPage;

Given('User accesses the login page', function () {
loginPage = new LoginPage();
loginPage.navigateTo();
});

When('Enters email address {string}', function (email: string) {
loginPage.enterEmail(email);
});

When('Enters password {string}', function (password: string) {
loginPage.enterPassword(password);
});

When('Clicks login button', function () {
loginPage.clickLogin();
});

Then('Dashboard page is displayed', function () {
expect(loginPage.isDashboardDisplayed()).to.be.true;
});

Then('Welcome message is displayed', function () {
expect(loginPage.getWelcomeMessage()).to.include('Welcome');
});

4. Cucumber Configuration​

cucumber.js

module.exports = {
default: {
require: ['features/step_definitions/**/*.ts'],
requireModule: ['ts-node/register'],
format: ['progress', 'html:reports/cucumber-report.html'],
publishQuiet: true
}
};

5. Execute Test​

npx cucumber-js

.NET (C# + SpecFlow)​

SpecFlow is a BDD framework equivalent to Cucumber in the .NET ecosystem.

1. Project Setup​

# Create .NET test project
dotnet new nunit -n MyApp.Specs
cd MyApp.Specs

# Install SpecFlow and other dependencies
dotnet add package SpecFlow.NUnit
dotnet add package SpecFlow.Tools.MsBuild.Generation
dotnet add package FluentAssertions
dotnet add package Microsoft.NET.Test.Sdk

2. Create Feature File​

Features/Login.feature

Feature: User Login Function
Verify that users can log in to the system

Scenario: Login with valid credentials
Given User accesses the login page
When Enters email address "user@example.com"
And Enters password "securepass"
And Clicks login button
Then Dashboard page is displayed
And Welcome message is displayed

3. Implement Step Definitions​

Steps/LoginSteps.cs

using TechTalk.SpecFlow;
using FluentAssertions;

namespace MyApp.Specs.Steps
{
[Binding]
public class LoginSteps
{
private LoginPage _loginPage;

[Given(@"User accesses the login page")]
public void UserAccessesLoginPage()
{
_loginPage = new LoginPage();
_loginPage.NavigateTo();
}

[When(@"Enters email address ""(.*)""")]
public void EnterEmailAddress(string email)
{
_loginPage.EnterEmail(email);
}

[When(@"Enters password ""(.*)""")]
public void EnterPassword(string password)
{
_loginPage.EnterPassword(password);
}

[When(@"Clicks login button")]
public void ClickLoginButton()
{
_loginPage.ClickLogin();
}

[Then(@"Dashboard page is displayed")]
public void DashboardPageIsDisplayed()
{
_loginPage.IsDashboardDisplayed().Should().BeTrue();
}

[Then(@"Welcome message is displayed")]
public void WelcomeMessageIsDisplayed()
{
_loginPage.GetWelcomeMessage().Should().Contain("Welcome");
}
}

// Test target class (example)
public class LoginPage
{
private string _email;
private string _password;
private bool _isLoggedIn;

public void NavigateTo()
{
// Implementation of page transition
}

public void EnterEmail(string email)
{
_email = email;
}

public void EnterPassword(string password)
{
_password = password;
}

public void ClickLogin()
{
// Actual authentication logic
if (_email == "user@example.com" && _password == "securepass")
{
_isLoggedIn = true;
}
}

public bool IsDashboardDisplayed()
{
return _isLoggedIn;
}

public string GetWelcomeMessage()
{
return _isLoggedIn ? "Welcome" : string.Empty;
}
}
}

4. SpecFlow Configuration​

specflow.json

{
"language": {
"feature": "en-US"
},
"bindingCulture": {
"name": "en-US"
},
"stepAssemblies": [
{
"assembly": "MyApp.Specs"
}
]
}

5. Execute Test​

dotnet test

SpecFlow Specific Features​

Using Hooks

[Binding]
public class Hooks
{
[BeforeScenario]
public void BeforeScenario()
{
// Initialization before each scenario execution
Console.WriteLine("Scenario Start");
}

[AfterScenario]
public void AfterScenario()
{
// Cleanup after each scenario execution
Console.WriteLine("Scenario End");
}

[BeforeFeature]
public static void BeforeFeature()
{
// Processing before Feature execution
}

[AfterFeature]
public static void AfterFeature()
{
// Processing after Feature execution
}
}

Using ScenarioContext

[Binding]
public class SharedSteps
{
private readonly ScenarioContext _scenarioContext;

public SharedSteps(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}

[Given(@"Set value ""(.*)"" to variable ""(.*)""")]
public void SetValueToVariable(string value, string key)
{
_scenarioContext[key] = value;
}

[Then(@"Value of variable ""(.*)"" is ""(.*)""")]
public void CheckValueOfVariable(string key, string expectedValue)
{
_scenarioContext[key].Should().Be(expectedValue);
}
}

BDD Best Practices​

1. How to Write Scenarios​

✅ Good: Concrete and Clear​

Given User "Taro Tanaka"'s account balance is 10000 yen
When Withdraw 5000 yen
Then Account balance becomes 5000 yen

❌ Bad: Ambiguous and Abstract​

Given User has money
When Withdraw money
Then Balance decreases

2. Given-When-Then Principle​

  • Given: Set initial state of the system (multiple allowed)
  • When: Action to test (usually one)
  • Then: Verification of expected result (multiple allowed)

3. Declarative vs Imperative​

When User creates a new article

❌ Imperative: Focus on "How"​

When Click "Create New" button
And Enter "Test Article" in title field
And Enter "This is a test" in body field
And Click "Publish" button

4. Independence and Reusability​

  • Make each scenario independently executable
  • Create Step Definitions with reusable granularity
  • Do not share state between scenarios

Summary​

Benefits of BDD​

  • ✅ Improved Communication: Entire team understands requirements in common language
  • ✅ Clarification of Specifications: Eliminate ambiguity and concretize requirements
  • ✅ Living Documentation: Functions as always up-to-date specification
  • ✅ Early Detection: Discover requirement discrepancies in early development stages

Challenges of BDD​

  • ❌ Learning Cost: Entire team needs to understand Gherkin and BDD concepts
  • ❌ Maintenance Cost: Maintenance of Feature/Step Definitions is required
  • ❌ Execution Speed: E2E tests take time to execute

Applicable Scenarios​

BDD is particularly effective in the following cases:

  • When requirements are complex and agreement among stakeholders is important
  • When dealing with business logic where domain knowledge is important
  • When collaboration between QA, Product Owner, and Developers is required
  • When automation of acceptance tests is necessary

Differentiation from TDD​

  • BDD: Testing user stories and acceptance criteria (from outside)
  • TDD: Testing internal implementation and algorithms (from inside)

By combining both, you can "build the right thing, right".