The BDD Living Documentation Model
A Gherkin feature file looks like a requirements document, reads like a requirements document, and is often written by people who have never opened a Python file - yet it also compiles down to running, pass/fail test code. BDD Decision Checklist covers when that trade-off is worth making versus writing plain pytest; this page explains the mechanism that makes it possible at all: how a scenario written in plain English becomes an executable claim about a running system, and why that combination is called "living documentation" rather than just "another test format."
The phrase matters because it names a specific property most documentation lacks: a design doc or a wiki page can drift out of sync with the code silently, read as accurate for months after it stopped being true, while a Gherkin scenario wired to real step definitions fails its build the moment the described behavior and the actual behavior diverge - it cannot go stale without announcing it.
Summary
- A Gherkin scenario is a specification written in a structured, human-readable format that also compiles into an executable test, so it can only drift from reality by visibly failing rather than quietly going stale.
- Insight: Ordinary documentation has no mechanism forcing it to stay accurate; wiring specifications to real step definitions gives a team a build-breaking signal the moment the spec and the system disagree.
- Key Concepts: Gherkin, step definition, living documentation, specification by example, the three amigos, test double.
- When to Use This Model: Aligning product, QA, and engineering on acceptance criteria before implementation, documenting cross-team API behavior in a form non-engineers can review, and deciding whether a given behavior deserves an executable spec versus a plain pytest test.
- Limitations/Trade-offs: BDD adds a real translation layer (Gherkin to step definitions to application code) that plain pytest doesn't need, and scenarios that describe implementation steps instead of behavior quietly turn back into brittle integration tests wearing a English-language costume.
- Related Topics: the pytest testing model, integration versus end-to-end testing, acceptance criteria and requirements engineering, continuous integration gates.
Foundations
Behavior-Driven Development (BDD) did not originate as a testing technique so much as a communication one: it grew out of the observation that "test-driven development" language ("test," "assert," "should") was confusing to non-engineers, and that describing behavior in terms of given some context, when an action happens, then an outcome follows gave product owners, QA, and engineers a shared vocabulary to describe the same feature without translation loss.
Gherkin is the concrete syntax that vocabulary settled into: Feature, Scenario, Given/When/Then keywords structuring plain-text steps into something both a human and a parser can read the same way. Gherkin Syntax covers that grammar in full; the mental model worth holding here is that Gherkin is not prose about the system, it is a formal-enough language that a tool can parse it into a scenario tree and drive execution from it.
A step definition is the Python function that gives a Gherkin line its actual meaning - the sentence "Given an authenticated billing clerk" means nothing to the interpreter until a step definition tells it what code to run when that exact (or pattern-matched) text appears. That binding is what closes the loop between plain-English specification and running code, and it is also exactly the layer that can rot: a scenario reads correctly to a human long after its step definition silently stopped calling the right application code, which is why keeping steps thin and traceable to real application logic matters as much as writing good scenarios.
A useful analogy: a Gherkin feature file is a contract written in language both parties can read, and step definitions are the notarization that makes the contract enforceable - the English text alone is just an agreement in principle, but wiring it to executable steps is what lets either side prove, automatically, whether the agreement is currently being honored.
Feature: Invoice creation
Scenario: Valid invoice
Given an authenticated billing clerk
When they create an invoice for 100.00 USD
Then the invoice status is "draft"Mechanics & Interactions
Turning a feature file into a running test is a three-layer translation, and naming the layers explicitly is what keeps them from collapsing into each other over time. The feature file describes what should happen in domain language, aimed at a business audience. The step definition is thin glue that maps a step's text (often via a pattern like parsers.parse('a user with email "{email}"')) to a Python call, aimed at the engineer wiring specs to code. The application service underneath that is where the real logic actually lives, aimed at whoever maintains the system's behavior. Step Definitions covers the parsing and context-passing mechanics in depth; the model worth internalizing here is that a step definition calling directly into the application's real service layer - not reimplementing logic inline - is what keeps a scenario testing the actual system instead of a parallel fiction.
Tools like behave and pytest-bdd differ mainly in how they wire that binding rather than in what the binding represents: behave runs feature files directly as its own runner with a context object threading state between steps, while pytest-bdd generates pytest test functions from feature files and lets fixtures carry that same shared state, which is why pytest-bdd scenarios can request ordinary pytest fixtures (a database session, an API client) the same way a plain pytest test would. behave / pytest-bdd Setup covers configuring either; the important point is that both are executing the same three-layer model, just with a different runner underneath the step-matching mechanism.
The three amigos practice - a business representative, a tester, and a developer collaborating on a scenario before it's implemented - is worth naming as part of the mechanics rather than a soft-skills aside, because it directly determines whether the resulting scenario stays living documentation or decays into a brittle test. A scenario drafted by one role in isolation tends to either bake in implementation details a business stakeholder can't validate, or stay so vague an engineer can't implement it precisely - the collaborative step is what produces a scenario specific enough to automate and abstract enough to review.
Feature file (business language, "what")
-> Step definition (thin glue, pattern-matches text to a call)
-> Application service (real logic, "how")
-> Domain model
Advanced Considerations & Applications
The property that makes BDD valuable - a scenario that fails loudly when it stops matching reality - only holds as long as scenarios describe behavior rather than implementation steps, and this is where most BDD adoptions quietly go wrong. A step like "When they create an invoice for 100.00 USD" survives a refactor of the invoice creation endpoint's internals untouched; a step like "When I call POST /api/v2/invoices with this JSON body" breaks the moment the API's internal routing changes even if the actual business behavior is unaffected, turning a living-documentation asset into exactly the kind of brittle integration test BDD was meant to avoid. Gherkin to Code covers writing scenarios that stay on the behavior side of that line.
Running BDD suites in CI raises a scaling question that plain pytest suites mostly avoid: because scenarios are meant to be readable by non-engineers, teams are tempted to let the scenario count grow into the hundreds as a substitute for requirements documentation, at which point suite runtime and maintenance cost start competing with the readability benefit that justified BDD in the first place. Gherkin to CI Pipeline covers running these suites as a CI gate; the trade-off worth stating plainly is that BDD's real cost is the translation layer itself - a team gets shared vocabulary and stakeholder-readable specs, and pays for it in an extra layer of indirection every scenario has to pass through before it reaches actual application code.
| Approach | Strength | Weakness | Best Fit |
|---|---|---|---|
| Gherkin + step definitions (BDD) | Stakeholder-readable, catches behavior drift automatically | Extra translation layer; brittle if steps encode implementation details | Cross-functional acceptance criteria, regulated/audited behavior |
| Plain pytest | No translation layer, faster to write and run | Not directly readable by non-engineers | Developer-facing unit and integration tests |
| Markdown/wiki test plans | Fast to write, no tooling required | Can go silently stale with no failure signal | Pre-automation exploration, throwaway manual test notes |
Common Misconceptions
- "BDD is just pytest with extra syntax." It's a coordination protocol between business, QA, and engineering roles first - the executable step-definition layer is what makes that shared language enforceable, not the point of the exercise on its own.
- "Any English-language test description counts as living documentation." Only a scenario wired to real step definitions that call real application code gets the fail-loudly-on-drift property; an unwired description is just prose that can go stale like any other doc.
- "Writing scenarios in terms of API calls and endpoints is more precise, so it's better." It's more brittle - encoding implementation details in steps means a scenario breaks on internal refactors that don't change actual behavior, defeating the purpose of describing behavior at all.
- "More scenarios always means better coverage of requirements." A large scenario count without editorial discipline becomes a maintenance burden that competes with, rather than reinforces, the readability BDD was adopted for.
- "BDD replaces unit testing." They answer different questions - BDD verifies acceptance-level behavior in stakeholder-readable language, while unit tests verify narrow logic quickly; most healthy suites use both, not one instead of the other.
FAQs
What does "living documentation" actually mean in BDD?
A specification (the Gherkin feature file) that is wired to real, running step definitions, so it can only drift from the actual system's behavior by failing its build - it cannot silently go stale the way a wiki page or design doc can.
How does a plain-English Gherkin step turn into running code?
A step definition function is registered with a pattern matching that step's text; when the runner encounters a matching line in a feature file, it calls the corresponding Python function, which typically delegates to real application logic.
What's the difference between `behave` and `pytest-bdd` mechanically?
behave is its own standalone runner using a context object to pass state between steps; pytest-bdd generates ordinary pytest test functions from feature files and lets steps use standard pytest fixtures for shared state instead.
Why do good step definitions stay "thin"?
Because the step definition is glue, not logic - calling directly into the real application service keeps the scenario testing actual system behavior, and keeps a refactor of the application's internals from breaking the scenario unless the actual behavior changed.
What are the "three amigos" and why do they matter mechanically, not just culturally?
A business representative, tester, and developer collaborating on a scenario before implementation - a scenario written by only one role tends to either bake in implementation details or stay too vague to automate precisely, so the collaboration directly affects whether the resulting scenario is both automatable and stakeholder-readable.
When does a Gherkin scenario stop being "living documentation" and become a brittle test?
The moment its steps describe implementation details (specific endpoints, request bodies) instead of behavior - at that point it breaks on internal refactors unrelated to actual behavior change, losing the property that justified writing it as BDD in the first place.
Should I use BDD instead of unit tests?
No - they verify different things. BDD scenarios describe stakeholder-readable, acceptance-level behavior; unit tests verify narrow logic quickly. Most healthy test suites use both rather than choosing one over the other.
Why can a large number of Gherkin scenarios become a liability?
Beyond a certain count, suite runtime and scenario-maintenance cost start competing with the readability benefit that justified adopting BDD, especially if scenarios were added as a substitute for a requirements document rather than as genuine acceptance criteria.
Does Gherkin syntax itself enforce good scenario writing?
No - the syntax structures steps into Given/When/Then, but nothing prevents someone from writing implementation-detail steps; discipline about describing behavior instead of mechanics comes from review practice (like the three amigos), not from the grammar.
What's the relationship between a feature file, a step definition, and application code?
Three layers with different audiences: the feature file describes behavior for business readers, the step definition is thin glue mapping that text to a function call, and the application service underneath holds the real logic that the step definition delegates to.
Can Gherkin scenarios replace API documentation?
They can usefully describe user-facing behavior in the language stakeholders understand, but they aren't a substitute for a formal API contract (like an OpenAPI spec) describing request/response shapes precisely - the two serve different audiences and different levels of precision.
Why does BDD tooling insist on parsing feature files instead of just running arbitrary English as comments?
Because the parseability is what makes the file executable at all - a tool has to reliably turn Given/When/Then lines into calls to registered step definitions, which requires Gherkin to be a formal-enough grammar, not free-form prose.
Related
- BDD Decision Checklist - when Gherkin is worth the translation-layer cost versus plain pytest
- Gherkin Syntax - the Feature/Scenario/Given-When-Then grammar in full
- Step Definitions - parsing, context, and keeping steps thin
- behave / pytest-bdd Setup - wiring feature files to either runner
- Gherkin to Code - writing scenarios that describe behavior, not implementation
- Gherkin to CI Pipeline - running BDD suites as a build gate at scale
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.