Behavior-Driven Development (BDD)
Behavior-Driven Development extends Test-Driven Development by writing tests in plain language that describe behavior from a user's perspective. BDD bridges the gap between technical and non-technical stakeholders.
What is BDD?
Behavior-Driven Development (BDD) is a software development approach that:
- Describes application behavior in plain language (Given-When-Then)
- Focuses on user stories and business value
- Enables collaboration between developers, testers, and stakeholders
- Uses examples to specify requirements
- Builds upon TDD principles
BDD vs TDD
TDD (Test-Driven Development)
// Technical, developer-focused
test('add should return sum of two numbers', () => {
expect(add(2, 3)).toBe(5);
});
BDD (Behavior-Driven Development)
# Business-focused, readable by anyone
Feature: Calculator Addition
Scenario: Add two numbers
Given I have a calculator
When I add 2 and 3
Then the result should be 5
Key Differences
| Aspect | TDD | BDD |
|---|---|---|
| Language | Technical (code) | Plain language |
| Focus | Testing implementation | Describing behavior |
| Audience | Developers | Everyone (stakeholders, QA, developers) |
| Format | Test assertions | Given-When-Then scenarios |
| Goal | Correct code | Correct behavior |
Given-When-Then Pattern
The Given-When-Then pattern structures scenarios:
- Given - Initial context (preconditions)
- When - Action or event
- Then - Expected outcome
Example: User Login
Feature: User Authentication
Scenario: Successful login with valid credentials
Given I am on the login page
And I have a registered account with email "user@example.com"
When I enter email "user@example.com"
And I enter password "SecurePass123"
And I click the "Login" button
Then I should be redirected to the dashboard
And I should see a welcome message "Welcome back, John!"
Scenario: Failed login with incorrect password
Given I am on the login page
When I enter email "user@example.com"
And I enter password "wrongpassword"
And I click the "Login" button
Then I should see an error message "Invalid credentials"
And I should remain on the login page
And the password field should be cleared
Gherkin Syntax
Gherkin is the language used to write BDD scenarios. It's designed to be human-readable while being executable by testing frameworks.
Basic Structure
Feature: Brief description of the feature
As a [role]
I want [feature]
So that [benefit]
Background: (Optional - runs before each scenario)
Given common preconditions
Scenario: Description of specific behavior
Given [initial context]
When [event occurs]
Then [expected outcome]
Scenario Outline: Parameterized scenario
Given <parameter>
When <action>
Then <result>
Examples:
| parameter | action | result |
| value1 | act1 | res1 |
| value2 | act2 | res2 |
Keywords
- Feature - High-level description
- Scenario - Specific test case
- Scenario Outline - Parameterized scenario with examples
- Background - Common steps for all scenarios
- Given - Preconditions
- When - Actions
- Then - Expected outcomes
- And/But - Additional steps
- Examples - Data table for scenario outlines
BDD Example: E-commerce Checkout
Feature File
Feature: Shopping Cart Checkout
As a customer
I want to purchase items in my cart
So that I can receive the products I selected
Background:
Given I am logged in as "john@example.com"
And I have items in my shopping cart:
| Product | Quantity | Price |
| Widget | 2 | 10.00 |
| Gadget | 1 | 25.00 |
Scenario: Successful checkout with credit card
Given I am on the checkout page
When I select "Credit Card" as payment method
And I enter card number "4242424242424242"
And I enter expiry date "12/25"
And I enter CVV "123"
And I enter billing address:
| Street | 123 Main St |
| City | Springfield |
| State | IL |
| Zip | 62701 |
And I click "Place Order"
Then I should see "Order Confirmed!"
And I should receive an order confirmation email
And my cart should be empty
And I should see order number
And my payment method should be charged $45.00
Scenario: Checkout fails with insufficient funds
Given I am on the checkout page
When I select "Credit Card" as payment method
And I enter a card with insufficient funds
And I click "Place Order"
Then I should see an error "Payment failed: Insufficient funds"
And I should remain on the checkout page
And my cart should still contain items
And no order should be created
Scenario Outline: Validate credit card inputs
Given I am on the checkout page
When I enter card number "<card_number>"
And I enter expiry date "<expiry>"
And I enter CVV "<cvv>"
And I click "Place Order"
Then I should see error message "<error_message>"
Examples:
| card_number | expiry | cvv | error_message |
| 1234 | 12/25 | 123 | Invalid card number |
| 4242424242424242 | 12/20 | 123 | Card expired |
| 4242424242424242 | 12/25 | 12 | Invalid CVV |
| 4242424242424242 | 13/25 | 123 | Invalid expiration date |
Implementing BDD
1. Tools for BDD
JavaScript/TypeScript:
- Cucumber.js - Popular BDD framework
- Jest-Cucumber - BDD with Jest
- Playwright - E2E testing with BDD support
Java:
- Cucumber-JVM - Cucumber for Java
- JBehave - Alternative BDD framework
Python:
- Behave - BDD framework for Python
- pytest-bdd - BDD with pytest
C#/.NET:
- SpecFlow - Cucumber for .NET
- LightBDD - Lightweight BDD framework
2. Step Definitions (JavaScript Example)
Feature file: login.feature
Feature: User Login
Scenario: Successful login
Given I am on the login page
When I enter email "user@example.com"
And I enter password "password123"
And I click the "Login" button
Then I should see my dashboard
Step definitions: login.steps.js
const { Given, When, Then } = require('@cucumber/cucumber');
const { expect } = require('@playwright/test');
Given('I am on the login page', async function() {
await this.page.goto('http://localhost:3000/login');
});
When('I enter email {string}', async function(email) {
await this.page.fill('#email', email);
});
When('I enter password {string}', async function(password) {
await this.page.fill('#password', password);
});
When('I click the {string} button', async function(buttonText) {
await this.page.click(`button:has-text("${buttonText}")`);
});
Then('I should see my dashboard', async function() {
await expect(this.page).toHaveURL(/\/dashboard/);
await expect(this.page.locator('h1')).toContainText('Dashboard');
});
3. Running BDD Tests
# JavaScript (Cucumber)
npm run cucumber
# Java (Cucumber)
mvn test -Dcucumber.options="--tags @smoke"
# Python (Behave)
behave features/
# .NET (SpecFlow)
dotnet test
BDD Best Practices
1. Write Scenarios Before Code
Just like TDD, write scenarios before implementation.
# 1. Write scenario
Feature: User Registration
Scenario: Register with valid data
Given I am on the registration page
When I enter valid registration details
Then I should be registered successfully
# 2. Implement step definitions
# 3. Implement application code
# 4. Run tests (should pass)
2. Use Business Language
# ❌ Technical language
Scenario: POST /api/users returns 201
Given I send POST request to /api/users
When response code is 201
Then response.body.user.id exists
# ✅ Business language
Scenario: Register new user successfully
Given I want to create a new account
When I submit my registration form
Then my account should be created
And I should receive a welcome email
3. Focus on Behavior, Not Implementation
# ❌ Implementation details
Scenario: Database update
Given database table "users" exists
When I execute INSERT INTO users
Then row should be added to database
# ✅ User behavior
Scenario: Create user account
Given I have valid registration details
When I submit the registration form
Then my account should be created
And I should be able to login
4. Keep Scenarios Independent
# ❌ Dependent scenarios (bad)
Scenario: Create user
When I create user "john@example.com"
Scenario: Login as created user # Depends on previous scenario!
When I login as "john@example.com"
# ✅ Independent scenarios (good)
Scenario: Login with existing user
Given a user exists with email "john@example.com"
When I login as "john@example.com"
Then I should see my dashboard
5. Use Background for Common Steps
Feature: Shopping Cart
Background:
Given I am logged in as "customer@example.com"
And I have items in my cart
Scenario: View cart
When I navigate to cart page
Then I should see my cart items
Scenario: Remove item from cart
When I remove an item from cart
Then the item should be removed
6. Use Scenario Outlines for Multiple Cases
# ❌ Repetitive scenarios
Scenario: Validate email format - missing @
When I enter email "invalidemail.com"
Then I should see error "Invalid email"
Scenario: Validate email format - missing domain
When I enter email "invalid@"
Then I should see error "Invalid email"
# ✅ Scenario Outline
Scenario Outline: Validate email format
When I enter email "<email>"
Then I should see error "<error>"
Examples:
| email | error |
| invalidemail.com | Invalid email |
| invalid@ | Invalid email |
| @example.com | Invalid email |
Three Amigos Meeting
BDD encourages collaboration through Three Amigos meetings:
Participants:
- Business Analyst/Product Owner - Defines requirements
- Developer - Implements features
- Tester/QA - Validates behavior
Process:
- Discuss user story
- Write BDD scenarios together
- Clarify requirements through examples
- Agree on acceptance criteria
Example Discussion:
PO: "Users should be able to reset their password."
QA: "What happens if the email doesn't exist?"
Dev: "Should we rate-limit reset requests?"
Together: Write scenarios covering all cases:
- Valid email → Send reset link
- Invalid email → Show generic message (security)
- Too many requests → Rate limit error
- Expired reset link → Show error
BDD for Different Test Levels
Unit Level BDD
// Describe behavior at unit level
describe('User validator', () => {
describe('when email is valid', () => {
it('should not throw error', () => {
expect(() => validateEmail('user@example.com'))
.not.toThrow();
});
});
describe('when email is invalid', () => {
it('should throw validation error', () => {
expect(() => validateEmail('invalid'))
.toThrow('Invalid email format');
});
});
});
Integration Level BDD
Feature: API User Management
Scenario: Create user via API
Given the API is available
When I send POST to "/api/users" with:
"""
{
"email": "user@example.com",
"name": "John Doe"
}
"""
Then the response code should be 201
And the response should contain user id
And the user should exist in database
E2E Level BDD
Feature: Complete User Journey
Scenario: New user registration and first purchase
Given I am a new visitor
When I register for an account
And I verify my email
And I login to my account
And I browse products
And I add items to cart
And I complete checkout
Then I should receive order confirmation
And I should receive order confirmation email
Common BDD Mistakes
1. Too Technical
# ❌ Too technical
Scenario: API returns JWT token
When POST /api/auth with credentials
Then response contains JWT in Authorization header
# ✅ Business-focused
Scenario: User logs in successfully
When I login with valid credentials
Then I should be authenticated
And I should have access to my account
2. Too Much Detail
# ❌ Too detailed
Scenario: Fill registration form
When I click email field
And I type "u"
And I type "s"
And I type "e"
...
# ✅ Appropriate level of detail
Scenario: Complete registration
When I fill in my registration details
Then my account should be created
3. UI-Specific Steps
# ❌ Coupled to UI
Scenario: Search products
When I click search icon
And I type in textbox with id "search-input"
And I click blue button with class "submit-btn"
# ✅ Behavior-focused
Scenario: Search products
When I search for "laptop"
Then I should see laptop search results
Resources
Next Steps
- Learn TDD if you haven't already
- Practice writing Given-When-Then scenarios
- Set up Cucumber or similar BDD framework
- Organize "Three Amigos" meetings with your team
- Start with one feature and expand
Describe behavior, not implementation - make tests everyone can understand!