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".