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)
| Item | TDD | BDD |
|---|---|---|
| Focus | Correctness of code | Behavior of the system |
| Writer | Mainly developers | Developers + QA + Business side |
| Test Granularity | Unit test (Function/Method) | Scenario test (Function/Feature) |
| Description Language | Programming language | Natural language (Gherkin etc.) |
| Test Purpose | Quality assurance of code | Verification of requirement realization |
| Documentation | Low (For technicians) | High (Readable by anyone) |
| Development Process | Red -> Green -> Refactor | Spec 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
- Create Feature File: Describe Gherkin scenarios with
.featureextension - Create Step Definitions: Write implementation code corresponding to each step
- Execute Test: Cucumber reads Feature file and executes Step Definitions
- 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
✅ Declarative (Recommended): Focus on "What"
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".