Behaviour-driven development

From the conversation to the test run

BDD is three practices in a loop: agree on concrete examples, write them down where everyone can read them, then make them executable. This is how each step works, the mistakes that make teams give up on it, and which frameworks run the result.

checkout-promotions.feature
# From a conversation, not from a ticket.
Feature: Applying a promotion at checkout

  Rule: A discount never takes an order below delivery

    Example: A valid code reduces the order total
      Given a cart worth £100.00
      When the shopper applies the code "SPRING25"
      Then the order total should be £75.00

    Example: The discount stops at the delivery floor
      Given a cart worth £6.00
      And delivery costs £4.99
      When the shopper applies the code "SPRING25"
      Then the order total should be £4.99

The idea

What is behaviour-driven development?

An agreement about behaviour that happens to be executable. Not a test framework, not a file format, and not a job for one person at the end of the sprint.

Behaviour-driven development came out of Dan North teaching test-driven development and watching the same thing go wrong every time: people got stuck on the word test. Told to write a test first, they wrote assertions about methods. Told to write a sentence starting with should, they wrote down what the system was supposed to do — and then argued about it, productively, before anyone had written the code. He named the practice in a 2006 article.

That is the whole move. BDD is the practice of agreeing on concrete examples of how a system should behave before it is built, in language the business and the engineers both use, and then wiring those examples up so they run. The examples become the specification, the acceptance criteria and the regression suite at the same time, which is why they are worth the trouble.

The cost BDD attacks is not writing the wrong code. It is agreeing to build the wrong thing, which is far more expensive and far harder to see.

The hardest single part of building a software system is deciding precisely what to build.
Fred Brooks, No Silver Bullet — quoted in Cucumber's own BDD guide

Everything else — Gherkin, Cucumber, step definitions, the folder your .feature files live in — is machinery in service of that. Useful machinery, and the rest of this page is about getting it right. But a team that installs the machinery and skips the agreement has bought a slower test framework with an extra layer of indirection, and will correctly conclude that BDD was not worth it.

The loop

The three practices of BDD: discovery, formulation, automation

Discovery, formulation, automation. Per story, not per release — the cycle is small enough to run inside a sprint, and it is the ordering that does the work.

01

Discovery

What it could do

A short, structured conversation over real examples. Not a requirements review — the point is to find the cases where three people who thought they agreed turn out not to.

Concrete examples do that and abstract discussion does not. "Handle expired promotions gracefully" survives a meeting intact. "A code that expired yesterday, on an order already in the basket" does not.

Leaves behind Shared understanding, and a written list of what nobody in the room could answer.

02

Formulation

What it should do

Write the agreed examples down in a form that a person and a machine can both read. Usually Gherkin, though the format matters less than the property: the business reader has to be able to spot that an example is wrong.

This is also a second check on the conversation. Examples that cannot be written down cleanly were not as agreed as they seemed.

Leaves behind A feature file the person who asked for the feature can read and correct.

03

Automation

What it actually does

Bind each step to code, one at a time, letting the failing step drive the implementation. The scenarios go from a description of intent to a statement of fact about the running system.

Automated last, deliberately. Automating an example nobody agreed on just makes the wrong behaviour harder to change.

Leaves behind A suite that fails the moment the system stops matching what was agreed.

Practice 01

What is example mapping, and how do you run a session?

Matt Wynne's technique for the discovery step, and the reason it caught on is that it finishes. Twenty-five minutes, four colours of index card, one user story.

The three people in the room are sometimes called the three amigos: whoever wants the feature, whoever will build it, and whoever will try to break it. More people is not better. The output is a wall of cards that shows the shape of the story at a glance.

Story

As a shopper, I want to apply a promotion code at checkout so I pay less.

Rule

A valid code takes its stated percentage off the order.

Example

£100 cart, SPRING25 → £75.00

Example

£100 cart, code applied twice → £75.00, second attempt refused

Rule

A promotion never takes an order below its delivery cost.

Example

£6 cart, £4.99 delivery, SPRING25 → £4.99

Example

£4 cart, £4.99 delivery → order refused before the code is applied

Question

Does the floor apply per order, or per item?

Question

What happens to a code that expires while the basket is open?

  • Story
  • Rule
  • Example
  • Question
One story, two rules, the examples that illustrate them, and the two things nobody in the room could answer.

Read the wall, not just the cards

The layout is the diagnosis. That is the part people miss when they copy the technique as a meeting format.

Lots of red cards

The story is not ready. Stop, take the questions away, and do not estimate it yet.

Lots of blue cards

The story is too big. Each rule is a candidate story of its own.

A blue card with no green cards under it

Nobody actually agrees what that rule means. It is the one most likely to be built wrong.

A blue card with fifteen green cards under it

You are enumerating data, not behaviour. One Scenario Outline, or a unit test.

No red cards and few green ones

Either the story is genuinely trivial, or the conversation was too polite. Usually the second.

The session ends when the group is satisfied the scope is clear, or when the twenty-five minutes run out. Both are results. A story that hits the timebox with a wall full of red cards has told you something valuable and cost you less than half an hour to learn it.

Practice 02

Gherkin syntax: what every keyword in a feature file does

About a dozen keywords and roughly three that people get wrong. Here is the whole vocabulary in one file, with the traps marked.

checkout-promotions.featureannotated
@checkout @promotions

Tags. The runner filters on these — one way to keep a slow suite out of the pull-request build.

Feature: Applying a promotion at checkout

Names one capability. One feature per file.

A promotion reduces what the shopper pays, but never below

Free text. Never executed, read by everyone. This is where the "why" goes.

the cost of getting the order to them.
 
Background:

Runs before every scenario in the file. Context only — if it performs the action under test, the scenarios stop making sense on their own.

Given the shopper is signed in
 
Rule: A promotion never takes an order below its delivery cost

One business rule, and the scenarios under it are the examples that illustrate it. This is the blue card from the discovery session, written down.

 
Example: A valid code reduces the order total

Interchangeable with Scenario:. One behaviour, one outcome.

Given a cart worth £100.00

What is already true before anything happens. State, not an action.

When the shopper applies the code "SPRING25"

The one event under test. A second When is usually a second scenario.

Then the order total should be £75.00

What someone could observe. Not what a row in the database says.

 
Example: The discount stops at the delivery floor

The edge case. This is the one a conversation surfaces and a ticket does not.

Given a cart worth £6.00
And delivery costs £4.99

And inherits the keyword above it. Cucumber binds it as a Given.

When the shopper applies the code "SPRING25"
Then the order total should be £4.99
But no delivery charge should be waived

But is And with better manners. Identical at runtime.

Two things the syntax will not tell you

The keyword is not part of a step's identity. Cucumber puts every step definition in one registry and matches on the text alone, so a definition registered with Given will happily match a line that starts with When. The keywords are there for the reader. Which means Given the shopper is signed in and When the shopper is signed in are the same step, and defining both is a duplicate-step error, not two behaviours.

And and But have no meaning of their own. At runtime they take the type of the keyword above them. They exist so that four consecutive Given lines read like English instead of like a form. If you find yourself writing And five times, that is usually a sign the scenario is doing setup that belongs in a Background or in a single higher-level step.

Parameterising a scenario

When the same behaviour needs illustrating at several boundaries, a Scenario Outline runs one scenario once per row of its Examples table. Each <placeholder> is substituted from the matching column.

checkout-promotions.feature
Scenario Outline: The discount stops at delivery cost
    Given a cart worth <cart>
    And delivery costs £4.99
    When the shopper applies the code "SPRING25"
    Then the order total should be <total>

    Examples:
      | cart    | total  |
      | £100.00 | £75.00 |
      | £20.00  | £15.00 |
      | £6.00   | £4.99  |

Three rows, three boundaries: well above the floor, comfortably above it, and pushed through it. That is an outline earning its keep. Thirty rows of the same path through the system is a data-driven unit test that has been dressed up as a specification, and it will be slow and unreadable in both roles.

Practice 02

How to write good Gherkin: declarative, not imperative

The one rule that fixes most bad scenarios is to describe the behaviour, not the mechanics. Both scenarios below test the same thing. Only one of them survives a redesign.

Imperative

A recording of a browser session

imperative.feature
Scenario: Discount
  Given I open "/cart"
  When I click "#promo-toggle"
  And I type "SPRING25" into the "promo" field
  And I press the "Apply" button
  Then ".cart-total" should contain "75.00"

Five steps, none of which mentions a promotion, a discount or a total. A product owner reading this learns that someone clicked something.

Every line is coupled to the interface. Rename the CSS class, move the promo field into a drawer, ship a mobile layout — the scenario breaks, and the behaviour it was guarding never changed.

Declarative

A statement about the system

declarative.feature
Scenario: A valid code reduces the order total
  Given a cart worth £100.00
  When the shopper applies the code "SPRING25"
  Then the order total should be £75.00

Three steps, and the numbers in them are exactly the ones the outcome depends on. The rule being specified is legible without knowing anything about the front end.

The clicking still has to happen — it moves into the step definition, where it belongs and where it is written once instead of in every scenario that touches checkout.

The rest of the anti-patterns, and what each one costs

Incidental detail

Every value in a scenario reads as significant. If the outcome does not depend on the shopper being called Frieda, the name is noise the next reader has to rule out.

Conjunction steps

“Given I log in and go to checkout and add two items” is one step with three reasons to fail, and the failure message tells you which of the three only by luck.

The scenario as a script

Eight When steps in a row is a test script. It fails at step six and you learn that something in a long sequence broke — which is what you already knew.

A Then that repeats the When

“When the code is applied / Then the code should be applied” asserts nothing. A Then has to name something observable that was not true before.

Background doing the work

A Background that signs in, fills a basket and opens checkout runs before every scenario in the file and hides the setup that actually mattered to any one of them.

Steps that name selectors

The moment a step mentions #promo-toggle, the feature file has become front-end source code and stopped being readable by the person who asked for the feature.

Practice 03

Step definitions: how a Gherkin step calls your code

A step definition is a pattern plus a function. The runner reads each line of the feature file, finds the one pattern that matches it, and calls what is attached.

In the feature file

Then the order total should be £75.00

In the step definitions, Java

@Then("the order total should be £{double}")
public void theOrderTotalShouldBe(double expected) {
    assertEquals(expected, checkoutPage.total(), 0.001);
}
The {double} is a Cucumber Expression parameter. It captures 75.00 out of the sentence and passes it in as an argument, which is why one definition serves every scenario that asserts on a total. Regular expressions do the same job with more power and less readability; most runners accept either.

Step text is a global namespace

This is the part that decides whether a BDD suite is maintainable at two hundred scenarios. Step definitions are matched across the whole project, not per file, so every sentence anyone writes competes with every sentence already there. Two consequences:

  • Two patterns that both match a line is an ambiguous-step error, and the runner refuses to guess.
  • Two patterns that nearly match — a cart worth £100.00 against a basket worth £100.00 — is worse, because nothing errors. You get two implementations of one idea, they drift, and the suite slowly acquires a second vocabulary for the same domain.

So the highest-leverage habit in the automation step is looking for an existing step before writing a new one. It is also the thing a person is worst at, because it requires remembering sentences written by someone else eight months ago.

Which is the practical argument for generating step definitions against the steps a project already has rather than from scratch — reuse is a search problem, and search is not a thing to do from memory. Generate a feature file and the step definitions come out using the conventions of the framework you picked.

Neighbours

BDD vs TDD vs ATDD

Three answers to three different questions, routinely presented as competitors. They are layers, and a healthy project runs at least two of them.

Comparison of test-driven, behaviour-driven and acceptance-test-driven development
 TDDBDDATDD
Question it answersDoes this unit do what I meant it to?Does the system behave the way we agreed it would?Is this acceptance criterion met?
Written byThe developer, aloneProduct, development and test togetherThe customer or analyst, with development and test
Written inThe production languageThe language of the domainThe language of the domain
GranularityA function or a classOne user-visible behaviourOne acceptance criterion
Typical countThousandsTens to low hundredsOne or more per story
Breaks whenInternal logic changesObservable behaviour changesThe criterion stops being satisfied
Functional tests help us build the right product. Unit tests help us build the product right.
Eric Elliott, Behavior Driven Development and Functional Testing

The practical reading of that: scenarios are the wrong tool for exhaustive coverage. A system needs orders of magnitude more low-level tests than it has behaviours worth describing to the business. A suite made entirely of Gherkin is slow, and when it fails it points at a page rather than at a line.

Use scenarios for the behaviours somebody outside the team cares about. Use unit tests for everything underneath. If a scenario exists only because a developer wanted coverage of a branch, it is a unit test that has been made ten times slower and given an audience that will never read it.

The honest part

When BDD is not worth it

Worth knowing before you commit a team to it, and rarely stated on pages that are selling something.

The conversation is the expensive bit, and it is not optional

Twenty-five minutes with three people, per story, every sprint. That is the actual price. Teams that will not pay it end up with the syntax and none of the benefit, which is strictly worse than not adopting it.

On a very small team the ceremony can exceed the gain

Two people who sit together and already talk through every story are doing discovery continuously. Formalising it can still help — writing examples down catches things a conversation does not — but the meeting structure is overhead they may not need.

The step layer is code you now maintain

Every scenario is a set of sentences plus the patterns and functions behind them. That layer needs refactoring, review and a naming convention like any other code, and it is the part that rots first because it is nobody’s favourite thing to work on.

A stale feature file lies with authority

The selling point is living documentation, and the failure mode follows from it: a feature file that no longer matches the system is believed, because it looks official and it used to be true. Requirements that churn weekly produce exactly this.

Readable does not mean the business will read it

Gherkin is readable by non-technical stakeholders. Whether any of them open the repository is a separate question, and the honest answer on most teams is no. If nobody outside the team ever reads a scenario, the format is buying you less than you think.

It does not make a slow suite fast

Scenarios drive the system end to end, which is why they catch integration problems and why they take minutes rather than milliseconds. Tag them, run a subset per pull request, and keep the full suite off the critical path.

The runners

BDD frameworks: which runners execute Gherkin, and in which languages

Gherkin is one syntax, but binding it to code is not one problem. Each runner below has its own glue mechanism, its own directory conventions, and its own way of failing quietly when those are wrong. This is what Gherkinizer generates for each, and where the output belongs once you have it.

Frameworks, supported languages, project layout and run command
FrameworkLanguagesFeature files go inRuns with
Selenium WebDriver
JavaPythonJavaScript/TypeScriptC#Ruby
src/test/resources/featuresmvn test
Playwright
JavaScript/TypeScriptPythonJavaC#
featuresnpx cucumber-js
Cypress
JavaScript/TypeScript
featuresnpx cypress run
REST Assured
Java
src/test/resources/featuresmvn test
Appium
JavaPythonJavaScript/TypeScriptC#Ruby
src/test/resources/featuresmvn test
SpecFlow
C#
Featuresdotnet test

The layout and command shown are for each framework's first language; the others follow their own conventions and the export uses whichever you pick.

Feature files and edge cases are not language-gated at all. Step definitions are free in Java; the other four languages are part of Pro.

Per framework

Cucumber setup guides, by framework

The configuration each runner needs, the traps specific to it, and a worked example end to end.

Cypress

Cucumber and Gherkin for Cypress

The Cypress Cucumber preprocessor setup that works: the maintained package, the specPattern trap, step-definition resolution, and why steps must not be async.

Read the guide

Questions

Common BDD questions

About the practice. What the tool does is answered on the homepage.

What does BDD stand for?
Behaviour-driven development. It is a way of working, not a tool: the team agrees on concrete examples of how a feature should behave before it is built, writes those examples down in language everyone shares, and then makes them executable. Dan North named it in 2006 after noticing that teams taught test-driven development got stuck on the word "test".
Is BDD the same thing as Cucumber?
No. Cucumber is one tool that runs Gherkin scenarios; BDD is the practice of discovering and agreeing those scenarios in the first place. A team can use Cucumber and not do BDD — that is the common case, and it usually means the scenarios were written after the code by one person, which buys an extra layer of indirection and no shared understanding. A team can also do BDD with no Gherkin at all.
Do I have to write Gherkin to do BDD?
No. Gherkin is a convenient format for the formulation step because it is readable by a product owner and parseable by a runner, but the value comes from the conversation that produces the examples. Plenty of teams do the discovery work, capture the examples in a wiki or a ticket, and automate them as ordinary tests. What you lose is the direct link between the sentence the business agreed to and the test that proves it.
Is BDD replacing TDD?
No, it grew out of it and sits above it. BDD scenarios describe user-visible behaviour, so a suite made only of them is slow and tells you only coarsely where something broke. Real systems need far more low-level tests than they have scenarios. Eric Elliott puts the split well: functional tests help you build the right product, unit tests help you build the product right.
Who writes the feature files?
The examples come from a conversation between the person who wants the feature, the person who will build it, and the person whose job is to break it — the "three amigos". Someone then writes them up, usually a tester or a developer. The test of whether it worked is not who typed it but whether the person who asked for the feature can read the result and say whether it is right.
How many scenarios should a feature file have?
Few enough that someone can read the file and understand the capability. If a file runs past roughly ten scenarios it is usually describing more than one capability, or it has started documenting data variations that belong in a Scenario Outline or in unit tests. A feature file is a specification a person reads, not a test-case inventory.
Does BDD work if the business will not get involved?
Partly, and you should be honest about which part. You keep the readable regression suite and the living documentation. You lose the discovery benefit, which is the one that pays for the rest — the whole argument for BDD is that catching a misunderstanding in a twenty-five minute conversation is cheaper than catching it in code review. Without that, plain unit and integration tests are usually the better trade.
What is the difference between a Scenario and a Scenario Outline?
A Scenario is one concrete example with fixed values. A Scenario Outline is the same scenario parameterised by placeholders in angle brackets, run once per row of its Examples table. Use an outline when the same behaviour is genuinely being illustrated at several boundaries; do not use it to run one path against thirty rows of data, which is a unit test wearing a costume.

The framework can wait. The scenarios cannot.

Gherkin is framework-agnostic, and a good feature file is worth having before anyone has decided what will execute it. The framework choice only starts to constrain things at the automation step, which is the last of the three.

Paste a requirement and get a feature file back, parsed with the official Cucumber grammar before you see it. Free, and no account needed for the first one.