BDD
Purpose: How to do BDD - Given-When-Then scenarios for stakeholder collaboration on acceptance criteria.
Behavior-Driven Development (BDD)¶
BDD is NOT a superset of TDD - they are different methodologies with different focus.
Key Distinction:
- TDD: Developer-driven (Red-Green-Refactor) - unit, integration, and acceptance levels
- BDD: Stakeholder-driven (Discovery - Formulation - Automation) - acceptance criteria in plain language
Relationship: Both can test the same system. TDD focuses on code correctness, BDD focuses on stakeholder-defined behavior. You can USE TDD to implement code that makes BDD scenarios pass.
BDD defines expected system behavior through collaboration between technical and non-technical stakeholders using plain-language scenarios.
The Three Pillars¶
1. Discovery: Product Owner, Developer, Tester collaborate to uncover concrete examples before implementation.
2. Formulation: Capture examples as Given-When-Then scenarios in business language.
3. Automation: Implement scenarios as executable tests (living documentation).
Given-When-Then Structure¶
Format: Gherkin syntax for scenarios
Feature: User Authentication
As a registered user
I want to log into my account
So that I can access my dashboard
Scenario: Successful login with valid credentials
Given a registered user with email "alice@example.com"
And a valid password "secure123"
When the user submits the login form
Then they should be redirected to the dashboard
And they should see a welcome message
Structure:
- Given - Context and preconditions
- When - Action or event
- Then - Expected outcome
Pillar 3: Automation¶
Implement scenarios as executable tests:
from pytest_bdd import scenario, given, when, then
@scenario('features/auth.feature', 'Successful login with valid credentials')
def test_successful_login():
pass
@given('a registered user with email "alice@example.com"')
def registered_user():
return User(email="alice@example.com")
@when('the user submits the login form')
def submit_login(registered_user):
return auth_service.login(registered_user.email, "secure123")
@then('they should be redirected to the dashboard')
def verify_redirect(submit_login):
assert submit_login.redirect_url == "/dashboard"
Benefits:
- Living documentation that stays current
- Business-readable test reports
- Fast feedback on behavior changes
Core BDD Practices¶
1. Collaboration First¶
Shift focus from code to behavior:
- Use plain-English descriptions
- All stakeholders can understand
- Align on requirements before implementation
Three Amigos pattern: PO defines “what”, Dev designs “how”, Tester asks “what about…?”
2. Scenario Quality¶
Focus on business behavior, not implementation:
Scenario: Calculate order discount
Given a customer with premium membership
When they place an order over $100
Then they should receive a 15% discount
Avoid technical details (database connections, method names, data types).
3. Declarative, Not Imperative¶
State what (declarative): “Given a user is authenticated”
Not how (imperative): “Given user opens login, enters email, enters password, clicks button…”
4. One Scenario Per Behavior¶
Each scenario tests one behavior. Avoid kitchen-sink scenarios testing “all order features”.
5. Maintain Scenarios¶
Revisit when requirements change, remove obsolete scenarios.
- Prevent test suite bloat
- Keep scenarios aligned with business needs
BDD Anti-Patterns¶
Too technical:
# BAD
Given the database has a record with id=123
When the API endpoint /api/v1/users/123 receives a GET request
Then the response JSON should have a "data" key
Too many scenarios:
- Keep scenarios focused
- Avoid testing every permutation
- Use Scenario Outlines for data variations
Coupling to UI:
# BAD - Brittle UI coupling
When I click the button with id "submit-btn"
# GOOD - Behavior focused
When I submit the order
Combining TDD + BDD¶
BDD and TDD are different methodologies that can work together:
- BDD: Defines acceptance criteria in stakeholder language (Given-When-Then)
- TDD: Implements components using Red-Green-Refactor cycle
Strategy: Write BDD scenario, use TDD to implement components, BDD scenario passes.
See testing-strategy.md for detailed comparison.
Tools for BDD¶
Python ecosystem:
- pytest-bdd - Gherkin scenarios with pytest
- behave - Pure BDD framework
- Cucumber - Cross-language BDD
See pyproject.toml for installed BDD tools.
Run BDD scenarios: See
CONTRIBUTING.md
for make recipes. Run BDD scenarios with
uv run pytest tests/acceptance/.
When to Use BDD¶
Use BDD for:
- User-facing features
- Acceptance criteria
- Cross-team communication
- Integration tests
- API contracts
Consider alternatives for:
- Unit tests (use TDD)
- Performance tests
Store scenarios in: tests/acceptance/features/