BDD Testing
Write and maintain Behavior-Driven Development tests with Gherkin and Cucumber. Use when defining acceptance scenarios, writing feature files, implementing step definitions, running Three Amigos sessions, or diagnosing BDD test quality issues. Keywords: bdd, gherkin, cucumber, given when then, feature files, step definitions, acceptance criteria, three amigos, example mapping.
npx skills add github:pantheon-org/tekhne --skill testing--bdd-testingWhen to Use
Use this skill when behavior needs to be specified and validated in shared, business-readable scenarios.
When Not to Use
Do not use BDD feature files for low-level unit behavior that is internal and not stakeholder-facing.
Core Principles
- Specify observable behavior, not implementation details.
- Keep a shared language across product, engineering, and QA.
- Scenarios should be deterministic and independently executable.
- Feature files should remain readable by non-developers.
Deterministic Workflow
- Align on examples in a Three Amigos discussion.
- Write scenarios in Gherkin using Given/When/Then.
- Implement step definitions that map to business language.
- Execute Cucumber and review failures by scenario intent.
- Validation checkpoint: If scenarios fail due to incorrect business logic, loop back to step 2 to clarify requirements.
- Validation checkpoint: If scenarios fail due to step definition errors or missing steps, loop back to step 3 to fix implementation.
- Error recovery: Run
npx cucumber-js --dry-runto identify missing step definitions before full execution. - Error recovery: For async timing issues, verify step definitions properly await promises.
- Refactor scenarios and steps to remove duplication.
Quick Commands
Run all feature tests
npx cucumber-js features
Expected result: scenario pass/fail summary and non-zero exit on failures.
Dry-run to detect missing step definitions
npx cucumber-js --dry-run features
Expected result: undefined steps listed without full execution.
Run a tagged subset
npx cucumber-js --tags "@smoke and not @wip"
Expected result: only matching scenarios execute.
Generate JSON report
npx cucumber-js --format json:reports/cucumber-report.json
Expected result: machine-readable execution report for CI/reporting.
Troubleshoot common Cucumber failures
Step definition mismatch:
npx cucumber-js --dry-run features
Expected result: lists undefined steps that need implementation or have mismatched patterns.
Async timing issues:
Check that step definitions return or await promises. Steps that don’t wait for async operations will complete before actions finish.
Pattern: Ensure async functions use await for all asynchronous calls.
Evaluate this skill quality
pantheon-skill-auditor evaluate bdd-testing --json
Expected result: updated quality dimensions and grade.
Constraints
Hard Constraints
- Use business-facing language in feature files.
- Keep each scenario focused on one behavior.
- Avoid cross-scenario state coupling.
Soft Constraints
- Scenario count per feature should stay manageable.
- Prefer explicit examples over abstract wording.
- Reuse step phrases only when semantics are identical.
Anti-Patterns
NEVER encode implementation details in Gherkin steps
WHY: Implementation-centric steps break when internals change.
BAD: When I click the submit button and call validateForm()
GOOD: When I submit the form
Consequence: Scenarios become brittle and unreadable to stakeholders.
NEVER skip Three Amigos before writing key scenarios
WHY: Missing perspectives create ambiguous or incomplete acceptance behavior.
BAD: Engineering writes scenarios alone from assumptions. GOOD: Product, QA, and engineering align on examples first.
Consequence: Rework increases and acceptance disputes appear late.
NEVER use vague Then steps without observable outcomes
WHY: Unverifiable outcomes cannot fail meaningfully.
BAD: Then it should work correctly
GOOD: Then I should see "Order confirmed"
Consequence: Tests pass without validating user-visible behavior.
NEVER couple scenarios with ordering dependencies
WHY: Scenario order dependence creates flaky suites.
BAD: Scenario B assumes data created by scenario A. GOOD: Each scenario creates or mocks its own prerequisites.
Consequence: Parallel runs and CI become unstable.
References
references/principles-core-philosophy.mdreferences/gherkin-syntax.mdreferences/principles-three-amigos.mdreferences/principles-example-mapping.mdreferences/cucumber-setup.md
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Runtime validation via eval scenarios proving the skill actually changes agent behaviour. Requires evals/ directory, instructions.json, ≥3 scenarios, and ≥80% instruction coverage in summary.json.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
No eval scenarios available.
Cucumber.js Setup and Installation
Installation
# Install Cucumber.js
bun add -D @cucumber/cucumber
# Install TypeScript support
bun add -D typescript ts-node @types/node
Project Structure
features/
lobby/
create-lobby.feature
join-lobby.feature
game/
player-movement.feature
game-completion.feature
support/
world.ts # Custom World class
steps.ts # Step definitions
hooks.ts # Before/After hooks
cucumber.js # Cucumber configuration
TypeScript Configuration
cucumber.js
module.exports = {
default: {
require: ["features/support/**/*.ts"],
requireModule: ["ts-node/register"],
format: ["progress-bar", "html:reports/cucumber-report.html"],
formatOptions: { snippetInterface: "async-await" },
},
};
Running Cucumber
# Run all features
npx cucumber-js
# Run specific feature file
npx cucumber-js features/lobby/create-lobby.feature
# Run specific scenario by line number
npx cucumber-js features/lobby/create-lobby.feature:10
# Run with tags
npx cucumber-js --tags "@mvp and not @wip"
# Dry run (validate syntax without running)
npx cucumber-js --dry-run
# Generate step definition snippets for undefined steps
npx cucumber-js --format snippets
package.json Scripts
{
"scripts": {
"test": "cucumber-js",
"test:ci": "cucumber-js --profile ci",
"test:smoke": "cucumber-js --tags @smoke",
"test:wip": "cucumber-js --tags @wip"
}
}
Gherkin Syntax Fundamentals
Keywords
| Keyword | Purpose | Description |
|---|---|---|
Feature |
High-level description | What feature is being tested |
Scenario |
Test case | A single example of behavior |
Given |
Precondition | Setup state before action |
When |
Action | The behavior being tested |
Then |
Expected outcome | What should happen |
And |
Additional steps | Extends Given/When/Then |
But |
Contrasting step | Negative assertion (optional) |
Background |
Setup for all scenarios | Runs before each scenario in a feature |
Scenario Outline |
Data-driven tests | Run same scenario with different data |
Examples |
Test data | Data table for Scenario Outline |
Feature Structure
Feature: Game Lobby Management
As a player
I want to create and join game lobbies
So that I can play with other players
Background:
Given the game server is running
And the database is seeded with test data
Scenario: Host creates a new lobby
Given I am logged in as "Alice"
When I create a new lobby with mode "race"
Then the lobby should exist
And I should be the host
And the lobby should have 1 player
Scenario: Player joins an existing lobby
Given a lobby exists with host "Bob"
And I am logged in as "Charlie"
When I join the lobby
Then the lobby should have 2 players
And I should not be the host
Given/When/Then Pattern
Given: Arrange (setup preconditions)
- Sets up the initial state
- Creates test data
- Navigates to starting point
When: Act (perform action)
- The behavior being tested
- Usually ONE action per scenario
- Represents user interaction
Then: Assert (verify outcome)
- Expected results
- Validates behavior
- Can have multiple assertions
Scenario Outline (Data-Driven Tests)
Scenario Outline: Player score calculation
Given a player finishes in position <position>
When the game ends
Then the player should receive <score> points
Examples:
| position | score |
| 1 | 500 |
| 2 | 300 |
| 3 | 200 |
| 4 | 100 |
This generates 4 scenarios automatically.
Tags
Tags organize and filter scenarios:
@mvp @lobby
Feature: Lobby Management
@smoke
Scenario: Create lobby
Given I am logged in
When I create a lobby
Then the lobby should exist
@wip @bug-123
Scenario: Handle host disconnect
Given a lobby exists with 3 players
When the host disconnects
Then the next player should become host
Run specific tags:
# Run only @mvp scenarios
npx cucumber-js --tags "@mvp"
# Run @mvp but skip @wip
npx cucumber-js --tags "@mvp and not @wip"
# Run @smoke OR @critical
npx cucumber-js --tags "@smoke or @critical"
Core BDD Philosophy
What is BDD?
Behavior-Driven Development (BDD) is a collaborative software development approach that:
- Bridges the gap between business and technical teams
- Uses concrete examples to describe system behavior
- Creates living documentation that serves as tests
- Focuses on delivering business value
- Promotes shared understanding through conversation
Discovery > Development > Delivery
Discovery: Collaborate to understand requirements
- Hold Three Amigos sessions
- Explore with examples
- Challenge assumptions
- Build shared understanding
Development: Implement guided by examples
- Use examples as specifications
- Automate examples as tests
- Follow outside-in TDD
Delivery: Validate against real behavior
- Executable specifications provide confidence
- Living documentation stays current
- Regressions are caught early
Key Principles
- Collaboration is essential - BDD requires active participation from business, development, and testing
- Examples clarify requirements - Concrete examples reveal ambiguities and edge cases
- Automate what matters - Not everything needs to be automated, focus on high-value scenarios
- Think behaviors, not tests - Describe what the system does, not how it’s tested
- Iterate and refine - Scenarios evolve as understanding deepens
- Keep scenarios maintainable - Write clear, focused scenarios that are easy to update
Common Misconceptions
❌ “BDD is just testing with Cucumber” ✅ BDD is a collaborative practice; tools are just enablers
❌ “BDD means writing tests before code” ✅ BDD means discovering requirements through examples before implementation
❌ “BDD scenarios should test everything” ✅ BDD scenarios should document key behaviors; use unit tests for details
❌ “Only testers write scenarios” ✅ Business, developers, and testers collaborate on scenarios
❌ “BDD slows down development” ✅ BDD reduces rework by building the right thing the first time
Benefits of BDD
- Reduced rework: Build the right thing from the start
- Better collaboration: Shared understanding across roles
- Living documentation: Always up-to-date specifications
- Faster onboarding: New team members learn from scenarios
- Regression safety: Automated scenarios catch breaking changes
- Business confidence: Stakeholders see value being delivered
Remember: BDD is fundamentally about communication and collaboration. The goal is to build software that delivers real value by ensuring everyone has a shared understanding of what needs to be built.
Example Mapping
A structured workshop technique using colored cards to explore features through rules and examples. Helps teams quickly identify what they know, what they don’t know, and what needs clarification.
The Four Colors
| Color | Represents | Purpose |
|---|---|---|
| Yellow | User Story / Feature | The capability being built |
| Blue | Rules | Business rules and acceptance criteria |
| Green | Examples | Concrete scenarios illustrating rules |
| Red | Questions | Uncertainties that need answering |
Workshop Format
Time-boxed: 25-30 minutes maximum Participants: Business, Development, Testing (Three Amigos) Materials: Colored index cards or sticky notes
Step-by-Step Process
1. Start with the Story (Yellow Card)
┌─────────────────────────────────────┐
│ As a customer │
│ I want to apply discount codes │
│ So that I can save money on orders │
└─────────────────────────────────────┘
2. Identify Rules (Blue Cards)
┌─────────────────────────────────┐
│ Discount codes are case- │
│ insensitive │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ One discount code per order │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ Expired codes cannot be applied │
└─────────────────────────────────┘
- Provide Examples (Green Cards)
For each rule, add concrete examples:
Blue Rule: "Discount codes are case-insensitive"
└─ Green: "SAVE20" and "save20" both work
└─ Green: "SaVe20" also works
Blue Rule: "One discount code per order"
└─ Green: Apply "SAVE20" → Works
└─ Green: Then try "FREESHIP" → Rejected
Blue Rule: "Expired codes cannot be applied"
└─ Green: Code expired yesterday → Rejected
└─ Green: Code expires tomorrow → Works
4. Capture Questions (Red Cards)
┌─────────────────────────────────┐
│ ? Can codes be combined with │
│ promotional pricing? │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ ? What happens if item already │
│ discounted by 50%? │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ ? Who can create discount codes?│
└─────────────────────────────────┘
Example Mapping Session
Story: User Registration
Yellow Card (Story):
As a new visitor
I want to create an account
So that I can make purchases
Blue Cards (Rules):
- Email must be unique
- Password must be strong (8+ chars, number, special char)
- Age must be 18+
- Email verification required
Green Cards (Examples):
Rule 1: Email must be unique
- ✅ Register with “new@example.com” → Success
- ❌ Register with existing “taken@example.com” → Error: “Email already registered”
Rule 2: Password must be strong
- ❌ Password “abc” → Error: “Too short”
- ❌ Password “abcdefgh” → Error: “Needs number”
- ❌ Password “abcdefgh1” → Error: “Needs special character”
- ✅ Password “abcdefgh1!” → Success
Rule 3: Age must be 18+
- ❌ Age 17 → Error: “Must be 18 or older”
- ✅ Age 18 → Success
- ✅ Age 65 → Success
Rule 4: Email verification required
- ✅ Register → Email sent → Click link → Verified
- ❌ Login before verifying → Error: “Please verify email”
Red Cards (Questions):
- ❓ Can users change email after registration?
- ❓ How long is verification link valid?
- ❓ What if email bounces?
- ❓ Do we need parent consent for users under 18?
Visual Layout
┌────────────────────────────────────────────────────────┐
│ YELLOW: User Registration Story │
├────────────────────────────────────────────────────────┤
│ │
│ BLUE: Email unique BLUE: Password strong │
│ GREEN: new@test.com GREEN: "abc" fails │
│ GREEN: existing fails GREEN: "abc123!" works │
│ │
│ BLUE: Age 18+ BLUE: Email verification │
│ GREEN: 17 rejected GREEN: Click link works │
│ GREEN: 18 accepted GREEN: No click = blocked │
│ │
│ RED: Email change? RED: Link expiry? │
│ RED: Bounced emails? RED: Parent consent? │
└────────────────────────────────────────────────────────┘
When to Stop
Stop the session if:
Too Many Red Cards (Uncertainty)
RED RED RED RED RED RED
Signal: Not ready to develop. Need more discovery. Action: Schedule follow-up with stakeholders.
Too Many Blue Cards (Complexity)
BLUE BLUE BLUE BLUE BLUE BLUE BLUE
Signal: Story too large. Action: Split into multiple stories.
Just Right (Ready for Development)
YELLOW
BLUE BLUE BLUE
GREEN GREEN GREEN GREEN GREEN
RED RED
Signal: Clear rules, good examples, few questions. Action: Proceed to development.
From Example Mapping to Gherkin
After the session, convert cards to Gherkin scenarios:
Blue Rule + Green Examples → Gherkin Scenario
Blue: "Password must be strong"
Green: "abc" → Error "Too short"
Green: "abcdefgh1!" → Success
Becomes:
Scenario Outline: Password strength validation
When I register with password "<password>"
Then I should see "<result>"
Examples:
| password | result |
| abc | Too short |
| abcdefgh | Needs number |
| abcdefgh1 | Needs special character |
| abcdefgh1! | Registration successful |
Red Questions → Follow-up Tasks
RED: "How long is verification link valid?"
Actions:
- [ ] Ask product owner
- [ ] Document decision
- [ ] Add scenario when answered
Example Mapping Anti-Patterns
❌ Writing Code in Examples
GREEN: When POST /api/register with {email, password}
Then return 201 with JWT token
Instead:
GREEN: Register with valid email → Account created
❌ Too Many Rules for One Story
YELLOW: Complete checkout flow
BLUE: Validate cart
BLUE: Process payment
BLUE: Update inventory
BLUE: Send confirmation
BLUE: Calculate tax
BLUE: Apply shipping
BLUE: Validate address
BLUE: Check fraud rules
Too complex! Split into multiple stories.
❌ Abstract Examples
GREEN: Valid input → Success
GREEN: Invalid input → Error
Instead:
GREEN: Register with "alice@example.com" → Success
GREEN: Register with "not-an-email" → Error: "Invalid email format"
❌ Ignoring Red Cards
RED: What about international addresses?
RED: How do we handle gift cards?
RED: Can businesses register?
[Session ends, questions never answered]
Don’t ignore questions! Assign owners and follow up.
Example Mapping Variations
Remote Teams
Use virtual whiteboard tools:
- Miro
- Mural
- FigJam
- Google Jamboard
Digital colored cards work the same way.
Large Features
Break into multiple sessions:
Session 1: Story A - Core registration
Session 2: Story B - Email verification
Session 3: Story C - Social login
Spike Investigations
When too many red cards:
┌─────────────────────────┐
│ SPIKE: Research │
│ International address │
│ validation requirements │
└─────────────────────────┘
Questions to answer:
- RED: Which countries?
- RED: Postal code formats?
- RED: Address validation API?
Run spike, then reconvene for Example Mapping.
Benefits
Fast Feedback
25-30 minutes reveals if story is:
- Ready (few red cards, clear examples)
- Needs refinement (some red cards, follow up)
- Blocked (many red cards, stop and investigate)
Shared Understanding
All three perspectives (business, dev, test) align on:
- What we’re building
- Why we’re building it
- How we’ll know it works
Right-Sized Stories
Too many blue cards → Story too big → Split it Too few rules → Story too small → Combine with others
Uncovers Assumptions
Red cards surface:
- Missing requirements
- Edge cases no one considered
- Technical constraints
- Integration questions
Example Mapping → BDD Workflow
1. Example Mapping Session (30 min)
↓
2. Review and Prioritize Examples
↓
3. Write Gherkin Scenarios (from green cards)
↓
4. Implement Step Definitions
↓
5. Develop Feature (TDD)
↓
6. All Scenarios Pass → Done
Complete Example
Story: Password Reset
Session Output:
YELLOW: Password Reset
BLUE: Reset link sent to registered email
GREEN: Request reset for "user@example.com" → Email sent
GREEN: Request reset for "unknown@example.com" → Email sent (security: don't reveal if email exists)
BLUE: Reset link expires after 1 hour
GREEN: Click link after 30 minutes → Works
GREEN: Click link after 90 minutes → Expired error
BLUE: Multiple requests invalidate previous links
GREEN: Request reset twice → First link no longer works
RED: Rate limiting for reset requests?
RED: What if email bounces?
RED: Can user reset password while logged in?
Resulting Gherkin:
Feature: Password Reset
Rule: Reset link sent to registered email
Scenario: Request reset with registered email
Given user "alice@example.com" exists
When I request password reset for "alice@example.com"
Then I should receive reset email
And I should see "If email exists, reset link sent"
Scenario: Request reset with unknown email
When I request password reset for "unknown@example.com"
Then I should see "If email exists, reset link sent"
But no email should be sent
Rule: Reset link expires after 1 hour
Scenario: Use reset link within expiry window
Given I requested reset 30 minutes ago
When I click the reset link
Then I should be able to set new password
Scenario: Use reset link after expiry
Given I requested reset 90 minutes ago
When I click the reset link
Then I should see "Reset link expired"
Rule: Multiple requests invalidate previous links
Scenario: Request reset twice
Given I requested reset and received link
When I request another reset
Then the first link should no longer work
And the second link should work
Follow-up Tasks:
[ ] Product owner: Define rate limiting rules
[ ] Tech lead: Investigate email bounce handling
[ ] UX: Design in-app password reset flow
Related References
- @see principles-three-amigos.md - The three perspectives
- @see collaboration-example-mapping-workshop.md - Detailed workshop facilitation
- @see principles-specification-by-example.md - Using examples to drive development
- @see collaboration-discovery-workshop.md - Alternative discovery format
Living Documentation
BDD scenarios serve as executable specifications that remain synchronized with actual system behavior, providing always-current documentation that technical and non-technical stakeholders can trust.
Core Concept
Living Documentation is documentation that:
- Executes - Runs as automated tests
- Validates - Verifies actual behavior
- Updates - Fails when behavior changes
- Communicates - Readable by business stakeholders
Four Functions of BDD Scenarios
1. Executable Specifications
Feature: Promotional Discount Application
To attract customers and increase sales
As a marketing manager
I want to offer promotional discounts
Rule: Percentage discounts apply to order subtotal
Example: 20% off for orders over $100
Given I have a $150 order
When I apply a "20% off" promotion
Then my discount should be $30
And my order total should be $120
This scenario IS the specification AND the test.
2. Up-to-Date Documentation
Unlike traditional documentation:
- Never stale - Fails when behavior changes
- Self-verifying - Green = accurate, Red = outdated
- Automatic maintenance - Forces updates during development
3. Common Language Bridge
Feature: Subscription Management
# Define domain terms upfront
# A "subscriber" is a customer with an active subscription
# A "churned subscriber" has cancelled within last 30 days
# "Winback" is re-activating a churned subscriber
Scenario: Winback offer for churned subscriber
Given a churned subscriber from 15 days ago
When they visit the pricing page
Then they should see the winback offer
Developers, testers, and product managers all understand this.
4. Regression Safety Net
Every scenario documents and protects a behavior:
Scenario: Order total includes tax
Given I have a $100 order
And my state has 8% sales tax
When I view my order total
Then the total should be $108
If someone changes tax calculation logic, this scenario fails immediately.
Documentation Patterns
Link to External Documentation
@jira:PAY-123
@confluence:payment-rules
@since:v2.1
Feature: Payment Processing
# Links business context to technical implementation
# Updated: 2024-01-15
# Owner: Payments Team
Version Tracking
@deprecated
@replaced-by:new-payment-flow.feature
Scenario: Legacy payment flow
# Kept for backward compatibility until v3.0
# Will be removed: 2024-06-01
Document Business Rules
Feature: Order Refunds
Rule: Full refunds available within 30 days
Scenario: Refund within return window
Given an order placed 15 days ago
When customer requests refund
Then full refund should be processed
Scenario: Refund after return window
Given an order placed 45 days ago
When customer requests refund
Then refund should be denied
And customer should see "Return window expired"
Rule: Only one promotion per order
Scenario: Cannot stack promotions
Given I have $100 order with "10% off" applied
When I try to apply "Free shipping"
Then I should see "One promotion per order"
And only "10% off" should be active
Each Rule documents a business constraint with concrete examples.
Living Documentation Reports
Generate readable HTML reports from scenarios:
npx cucumber-js --format html:reports/features.html
Reports show:
- All features and scenarios
- Pass/fail status
- Execution time
- Tags and metadata
- Historical trends
Benefits
For Business Stakeholders
- Visibility - See what’s implemented vs planned
- Confidence - Green tests = working features
- Understanding - Plain language, concrete examples
For Developers
- Specification - Clear requirements before coding
- Regression protection - Know when behavior breaks
- Onboarding - New devs learn from scenarios
For Testers
- Test coverage - What’s tested is documented
- Edge cases - Captured in scenarios
- Automation - Tests run continuously
Best Practices
Document key behaviors, not every detail:
# GOOD - Documents important business rule
Scenario: Premium users get free expedited shipping
Given I am a Premium member
When I select expedited shipping
Then shipping cost should be $0
# AVOID - Too granular for documentation
Scenario: Button color changes on hover
When I hover over the submit button
Then the button should be #007bff
Keep scenarios readable:
# GOOD - Business language
Scenario: Checkout with insufficient inventory
Given product "Laptop" has 0 stock
When I try to purchase "Laptop"
Then I should see "Out of stock"
# AVOID - Technical details
Scenario: Database constraint violation
Given inventory table has stock_count = 0
When POST /api/orders with product_id = 123
Then response should be 409 Conflict
Update scenarios when requirements change:
# When business rule changes from 14 to 30 days,
# update scenario immediately:
Scenario: Refund available within 30 days # Was: 14 days
Given order placed 25 days ago # Was: 10 days ago
When customer requests refund
Then full refund should be processed
Anti-Patterns
❌ Outdated scenarios:
@ignore # Don't ignore! Fix or delete
Scenario: Old feature that changed
❌ Non-executable documentation:
# This is just a comment, not a runnable scenario
# Users can reset passwords via email
# Tokens expire after 1 hour
Instead:
Scenario: Password reset token expiration
Given I requested password reset 61 minutes ago
When I try to use the reset link
Then I should see "Reset link expired"
❌ Technical-only documentation:
Scenario: JWT refresh token rotation
When POST /auth/refresh with valid refresh_token
Then response includes new access_token
And old refresh_token is revoked
Better:
Scenario: Stay logged in with automatic token refresh
Given I logged in 55 minutes ago
When I continue using the application
Then my session should remain active
And I should not be logged out
Living Documentation Mindset
Think of scenarios as:
- Specifications first, tests second
- Business communication, technical automation
- Documentation that enforces itself
- Requirements that never lie
When scenarios fail, they’re telling you:
- “Behavior changed” (update scenario), or
- “Bug introduced” (fix code)
Never ignore failing scenarios. They’re your documentation screaming “I’m outdated!”
Example: Complete Living Documentation
Feature: Gift Card Purchase and Redemption
As a customer
I want to buy and use gift cards
So I can give gifts or save for later purchases
@since:v2.0
@owner:payments-team
Rule: Gift cards have minimum and maximum amounts
@acceptance
Scenario: Purchase $50 gift card
When I purchase a $50 gift card
Then a unique gift card code should be generated
And the gift card balance should be $50
@edge-case
Scenario: Minimum purchase amount enforced
When I try to purchase a $5 gift card
Then I should see "Minimum gift card amount is $10"
@edge-case
Scenario: Maximum purchase amount enforced
When I try to purchase a $1000 gift card
Then I should see "Maximum gift card amount is $500"
Rule: Gift cards can be partially redeemed
@acceptance
Scenario: Partial redemption
Given I have a gift card with $50 balance
When I apply it to a $30 order
Then $30 should be deducted from gift card
And remaining balance should be $20
And my order total should be $0
@edge-case
Scenario: Multiple partial redemptions
Given I have a gift card with $100 balance
When I apply it to a $30 order
And later apply it to another $40 order
Then remaining balance should be $30
This feature file is:
- ✅ Executable (runs as automated test)
- ✅ Documentation (explains gift card rules)
- ✅ Specification (defines required behavior)
- ✅ Communication tool (business + technical can read)
- ✅ Regression safety (catches breaking changes)
Related References
- @see principles-core-philosophy.md - Discovery-Development-Delivery cycle
- @see principles-ubiquitous-language.md - Shared vocabulary
- @see collaboration-stakeholder-involvement.md - Keeping stakeholders engaged
- @see patterns-domain-language.md - Writing business-readable scenarios
The Three Amigos Practice
A practice where three perspectives collaborate to explore and define features.
The Three Perspectives
1. Business Perspective (Product Owner/BA)
- What problem are we solving?
- What value does it provide?
- What are the business rules?
2. Development Perspective (Developer)
- How might we build this?
- What are the technical constraints?
- What are the edge cases?
3. Testing Perspective (Tester/QA)
- What could go wrong?
- What are we missing?
- How will we verify this works?
Example Three Amigos Session
Feature: Password Reset
Business: “Users who forget their password need a way to reset it via email.”
Developer: “We’ll need to generate secure tokens with expiration. How long should tokens be valid?”
Tester: “What happens if they request multiple reset emails? Can old tokens still be used?”
Business: “Tokens should be valid for 1 hour. Multiple requests should invalidate old tokens.”
Developer: “Should we rate-limit reset requests to prevent abuse?”
Tester: “What if the email address doesn’t exist in our system?”
Business: “For security, show the same success message whether or not the email exists.”
Outcome: Concrete Scenarios
Scenario: Request password reset with valid email
Given a user account exists for "user@example.com"
When I request a password reset for "user@example.com"
Then I should receive a reset email
And the reset link should be valid for 1 hour
Scenario: Request password reset with non-existent email
When I request a password reset for "nonexistent@example.com"
Then I should see a success message
But no email should be sent
Scenario: Multiple password reset requests
Given I have requested a password reset
When I request another password reset
Then the previous reset link should be invalidated
And I should receive a new reset email
Session Structure
1. Product Owner presents the user story
2. Developer asks clarifying questions
3. Tester identifies potential edge cases
4. Team writes scenarios together
5. Everyone agrees on acceptance criteria
Best Practices
- Schedule regular Three Amigos sessions
- Time-box to 30-60 minutes
- Focus on one story at a time
- Write scenarios during the session
- Capture questions for later follow-up
Ubiquitous Language
A shared vocabulary used consistently by both technical and business team members throughout code, conversations, and documentation. The foundation of effective BDD collaboration.
Core Concept
Ubiquitous Language is:
- Shared - Same terms used by everyone (dev, QA, product, stakeholders)
- Consistent - One concept = one term
- Domain-focused - Business concepts, not technical implementation
- Living - Evolves with domain understanding
- Everywhere - In code, scenarios, conversations, docs
Why It Matters
Without Ubiquitous Language
Product Manager: “When a customer’s subscription lapses, we need to win them back.”
Developer: “So you want to reactivate a churned user account?”
QA: “Are we testing the expired premium member re-enrollment flow?”
Same concept, three different terms. Confusion follows.
With Ubiquitous Language
Everyone: “When a subscriber churns, we offer a winback promotion.”
Terms defined:
- Subscriber - Customer with active subscription
- Churn - Cancel subscription
- Winback - Re-activate churned subscriber
Building Ubiquitous Language
1. Discovery Through Conversation
Ask questions during Three Amigos sessions:
What do you call this?
PM: "When someone stops paying..."
Dev: "What's the term for that? Cancelled? Inactive?"
PM: "We call them 'churned subscribers'"
✅ Term added to glossary: Churned Subscriber
What’s the difference between X and Y?
QA: "What's the difference between 'guest' and 'user'?"
PM: "Guest hasn't registered. User has an account."
✅ Two distinct terms: Guest, User
When does this state change?
Dev: "When exactly does an order become 'completed'?"
PM: "When payment clears and items ship."
✅ Term defined: Completed = Paid + Shipped
2. Document in Scenarios
Feature: Subscription Management
# Define domain terms upfront
Background:
"""
Domain Terms:
- Subscriber: Customer with active subscription
- Churned Subscriber: Cancelled within last 30 days
- Lapsed Subscriber: Cancelled over 30 days ago
- Winback Offer: Discount to re-activate churned subscriber
"""
Scenario: Winback offer for churned subscriber
Given a churned subscriber from 15 days ago
When they visit the pricing page
Then they should see the winback offer
Scenario: No winback for lapsed subscriber
Given a lapsed subscriber from 60 days ago
When they visit the pricing page
Then they should see standard pricing
And they should not see the winback offer
3. Use Domain Terms Consistently
❌ Inconsistent Terms:
Feature: User Account Management
Scenario: User registers
When a customer signs up
Then a member account is created
And the client receives confirmation email
Four terms (user, customer, member, client) for one concept!
✅ Consistent Terms:
Feature: Member Account Management
Scenario: Member registers
When a customer creates an account
Then a member account is created
And the member receives confirmation email
Distinction:
- Customer - Anyone (browsing, not logged in)
- Member - Registered account
4. Maintain a Glossary
# Domain Glossary
## Order Lifecycle
- **Cart** - Temporary collection of items (not yet ordered)
- **Order** - Finalized purchase (payment may be pending)
- **Pending Order** - Order created, awaiting payment
- **Confirmed Order** - Payment received, awaiting fulfillment
- **Shipped Order** - Items dispatched to customer
- **Completed Order** - Items delivered successfully
- **Cancelled Order** - Order terminated before shipping
- **Returned Order** - Items sent back after delivery
## Membership
- **Guest** - Browsing without account
- **Customer** - Anyone (guest or member)
- **Member** - Registered account (free tier)
- **Subscriber** - Paid subscription
- **Premium Member** - Highest paid tier
## Inventory
- **In Stock** - Available for immediate purchase
- **Low Stock** - Less than 10 units remaining
- **Out of Stock** - Zero units, cannot purchase
- **Backordered** - Out of stock, but can pre-order
- **Discontinued** - No longer sold
Ubiquitous Language in Code
Class Names
// GOOD - Uses domain language
class Subscriber {
churnDate?: Date;
isChurned(): boolean {
return this.churnDate !== undefined;
}
isEligibleForWinback(): boolean {
if (!this.isChurned()) return false;
const daysSinceChurn = daysBetween(this.churnDate, now());
return daysSinceChurn <= 30;
}
}
// AVOID - Technical jargon
class UserAccount {
deactivationTimestamp?: number;
isDeactivated(): boolean {
return this.deactivationTimestamp !== undefined;
}
}
Method Names
// GOOD - Domain verbs
order.confirm();
order.ship();
order.complete();
subscription.churn();
subscriber.winback();
// AVOID - Generic CRUD
order.update({ status: 'confirmed' });
subscription.delete();
user.create();
Enums and Constants
// GOOD - Domain states
enum OrderStatus {
Pending = 'PENDING',
Confirmed = 'CONFIRMED',
Shipped = 'SHIPPED',
Completed = 'COMPLETED',
Cancelled = 'CANCELLED',
}
// AVOID - Technical states
enum OrderStatus {
State1 = 1,
State2 = 2,
State3 = 3,
}
Ubiquitous Language in Scenarios
High-Level Business Terms
# GOOD - Business domain language
Feature: Order Management
Scenario: Checkout with insufficient inventory
Given product "Laptop" is out of stock
When customer tries to purchase "Laptop"
Then they should see "Out of stock"
And order should not be created
# AVOID - Technical implementation
Feature: Database Constraint Handling
Scenario: Handle zero inventory constraint
Given inventory.stock_count = 0 for product_id = 123
When POST /api/orders with product_id = 123
Then response should be 409 Conflict
And orders table should have no new records
Consistent Terminology
Feature: Subscription Billing
Scenario: Monthly subscription renewal
Given a subscriber with monthly billing cycle
When their subscription renews on billing date
Then they should be charged subscription fee
And subscription expiry should extend 30 days
# Use "subscriber" consistently, not:
# - "user"
# - "customer"
# - "member"
# - "account"
Common Pitfalls
1. Technical Terms in Business Scenarios
❌ Don’t expose implementation:
When I POST to /api/auth with credentials
Then JWT token is stored in localStorage
And user_id foreign key is inserted
✅ Use business language:
When I log in with valid credentials
Then I should be authenticated
And I should access my dashboard
2. Ambiguous Terms
❌ Vague:
Given the item is inactive
When I process the thing
Then the status should update
✅ Specific:
Given the product is discontinued
When I attempt to purchase the product
Then I should see "Product no longer available"
3. Synonyms for Same Concept
❌ Inconsistent:
Scenario: Register new account
When I sign up
Then I create a profile
And I complete onboarding
# "register", "sign up", "create profile", "onboarding" - which is it?
✅ Consistent:
Scenario: Register new member
When I complete registration
Then my member account should be created
And I should receive welcome email
4. One Term for Multiple Concepts
❌ Overloaded term “User”:
Given a user is logged in # Authenticated member?
And a user visits the page # Anonymous visitor?
And a user account exists # Member entity?
✅ Distinct terms:
Given a member is logged in
And a guest visits the page
And a member account exists with email "test@example.com"
Evolving Ubiquitous Language
Language evolves as domain understanding deepens:
Initial:
Scenario: Deactivate user
When admin deactivates user
Then user cannot login
After discussion:
Team realizes two types of deactivation:
- Temporary suspension (can be reversed)
- Permanent deletion (cannot be reversed)
Evolved:
Scenario: Suspend member account
When admin suspends member
Then member cannot login
But member data is retained
Scenario: Permanently delete member account
When admin deletes member
Then member cannot login
And member data is anonymized
Update glossary:
- ~~Deactivate~~ (deprecated - ambiguous)
- **Suspend** - Temporarily disable login (reversible)
- **Delete** - Permanently remove member (irreversible)
Benefits
Reduced Miscommunication
Everyone speaks the same language. No translation layer between business and technical.
Better Code Quality
Code mirrors business domain, making it easier to understand and maintain.
Effective Collaboration
Product, dev, and QA collaborate using shared terminology.
Living Documentation
Scenarios document business rules using terms everyone understands.
Faster Onboarding
New team members learn domain vocabulary from scenarios and code.
Building Ubiquitous Language - Workshop Exercise
Step 1: Brainstorm Terms
What do we call someone who:
- Visits the site? → Visitor? Guest?
- Creates account? → User? Member? Customer?
- Pays subscription? → Subscriber? Premium Member?
- Cancels subscription? → Churned? Inactive? Lapsed?
Step 2: Define Distinctions
Guest: Not logged in, browsing
Member: Free account, logged in
Subscriber: Paid monthly subscription
Churned Subscriber: Cancelled within 30 days
Lapsed Member: Cancelled over 30 days ago
Step 3: Document in Glossary
# Terms
- **Guest** - Visitor without account
- **Member** - Registered free account
- **Subscriber** - Paid subscription
- **Churned** - Cancelled subscription (last 30 days)
- **Lapsed** - Cancelled subscription (30+ days ago)
Step 4: Use in Scenarios
Scenario: Guest browses products
Given I am a guest
When I view products
Then I see public pricing
Scenario: Subscriber sees discounted pricing
Given I am a subscriber
When I view products
Then I see subscriber pricing
Step 5: Implement in Code
class Guest extends Visitor {
viewProducts(): Product[] {
return this.catalog.getPublicProducts();
}
}
class Subscriber extends Member {
viewProducts(): Product[] {
return this.catalog.getSubscriberProducts();
}
}
Related References
- @see principles-core-philosophy.md - BDD communication focus
- @see collaboration-building-ubiquitous-language.md - Team exercises
- @see principles-living-documentation.md - Document domain terms
- @see patterns-domain-language.md - Using domain terms in scenarios