Teaches behaviour-driven development end to end for a newcomer: what BDD is and how discovery, formulation and automation fit together; a decision table that picks the runner from the project's language and build files (Cucumber-JVM, Cucumber-JS, Cucumber-Ruby, Behave for Python, Reqnroll for .NET, and why SpecFlow is end-of-life); install and first-run commands for each; the declarative-versus-imperative Gherkin discipline with a worked bad-versus-good pair; Background, Scenario Outline and domain-organised step libraries; the traps that make BDD collapse into an expensive UI-automation wrapper; and an honest account of when BDD is not worth adopting. Use when a team is adopting BDD, choosing a Gherkin runner, or a *.feature file needs writing and nobody has settled the conventions.
70
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Behaviour Driven Development is "a way for software teams to work that closes the gap between business people and technical people" (cucumber.io/docs/bdd). It runs as three repeating practices: Discovery (talk through concrete examples of the new functionality), Formulation (document those examples so they can be automated), and Automation (implement each example, starting with an automated test that guides the code) (cucumber.io/docs/bdd).
The formulation language is Gherkin: "a set of special keywords to give
structure and meaning to executable specifications"
(cucumber.io/docs/gherkin/reference). A .feature file is plain
text. A step definition binds each Gherkin line to code in your language. A
runner parses the feature files, matches steps to definitions, and reports
results through your existing test framework (JUnit, pytest, NUnit, Mocha).
BDD is not a test level. It is a specification format that can drive a unit test, a service test or a full browser test; the step definitions decide which. The same scenario runs in milliseconds against a domain service and in seconds through a browser, so bind to the lowest layer that still exercises the behaviour the business named.
Discovery is a conversation between the Three Amigos (cucumber.io/docs/bdd/who-does-what): a Product Owner or BA who defines scope and user intent, a Tester who generates scenarios and edge cases, and a Developer who adds step detail and implementation constraints - each amigo sees the product from a different perspective.
Runner choice in BDD is almost entirely determined by your language and build system, because step definitions are written in that language. Read the repo, match a row, stop deliberating.
| What you find in the repo | Runner | Package to add |
|---|---|---|
pom.xml or build.gradle (Java, Kotlin, Scala, Groovy) | Cucumber-JVM | io.cucumber:cucumber-java plus cucumber-junit-platform-engine (cucumber.io/docs/installation/java, cucumber-jvm README) |
package.json (JavaScript or TypeScript on Node) | Cucumber-JS | @cucumber/cucumber (cucumber.io/docs/installation/javascript) |
Gemfile or *.gemspec (Ruby, Rails) | Cucumber-Ruby | gem 'cucumber', or cucumber-rails for Rails (cucumber.io/docs/installation/ruby) |
requirements.txt or pyproject.toml (Python) | Behave | behave (behave.readthedocs.io/install) |
*.csproj or *.sln (.NET, any test framework) | Reqnroll | Reqnroll.NUnit, Reqnroll.MsTest, Reqnroll.xUnit or Reqnroll.TUnit (docs.reqnroll.net setup) |
A .NET repo that already references SpecFlow.* packages | Migrate to Reqnroll | see the SpecFlow note below |
| No stakeholder outside engineering will ever read the feature files | None. Write tests directly in your test framework | see "When BDD is not worth it" |
SpecFlow was the standard .NET BDD runner for a decade, so most tutorials still
point at it. It is dead: Tricentis, which owned it, states "SpecFlow has been
retired" (shiftsync.tricentis.com), specflow.org now
redirects there, and it "reached its end-of-life on December 31, 2024" with the
GitHub projects deleted as of 1 January (reqnroll.net). The
packages still install only because nuget.org will not delete existing ones
(reqnroll.net) - exactly how newcomers land on an unsupported
dependency. Use Reqnroll, the maintained successor: "a reboot of the SpecFlow
project" (reqnroll.net), forked and renamed off the "SpecFlow"
Tricentis trademark, so migrating is mostly package and namespace renames, not a
rewrite (reqnroll.net).
Run the block that matches your row. Success looks the same everywhere: the runner reports undefined steps and prints copy-pasteable step-definition snippets. That is the correct first result, not a failure. The .NET (Reqnroll) path is below; the other four runners are in references/runner-setup.md.
# .NET (Reqnroll), per docs.reqnroll.net/latest/installation/setup-project.html
dotnet new install Reqnroll.Templates.DotNet
dotnet new reqnroll-project -t nunit -f net8.0 -o CheckoutSpecs
cd CheckoutSpecs && dotnet testThe template install and -t / -f flags are per
docs.reqnroll.net setup. To add Reqnroll to an existing .NET
test project instead of scaffolding, run dotnet add package Reqnroll.NUnit
(docs.reqnroll.net setup).
This is the part newcomers get wrong, and getting it wrong is what turns BDD into an expensive way to write browser automation.
"Your scenarios should describe the intended behaviour of the system, not the implementation. In other words, it should describe what, not how." (cucumber.io/docs/bdd/better-gherkin) The test to apply to every line: "Will this wording need to change if the implementation does?" (cucumber.io/docs/bdd/better-gherkin) If yes, the line is wrong.
Bad: imperative, UI-mechanical.
Scenario: Promo code
Given I open "https://shop.example.com/login"
And I type "ada@example.com" into "#email"
And I type "hunter2" into "#password"
And I click "#login-btn"
And I click ".product[data-sku='BOOK-001'] .add-to-cart"
And I click "#cart-icon"
And I type "WELCOME10" into "#promo-input"
And I click "#apply-promo"
Then the element "#subtotal" has text "$22.49"Nine steps, nine step definitions, four CSS selectors baked into the specification. Nobody in the business can read it, and a login redesign breaks it even though promo-code behaviour did not change.
Good: declarative, intent-level.
Scenario: Valid promo code reduces the subtotal
Given a logged-in customer with "BOOK-001" at $24.99 in their cart
When they apply promo code "WELCOME10"
Then the subtotal is $22.49Three steps. The selectors and the login mechanics move into the step definition code, where changing them is a one-line edit that no feature file sees. Declarative style "describes the behaviour of the application, rather than the implementation details" (cucumber.io/docs/bdd/better-gherkin).
A Background "allows you to add some context to the scenarios that follow it.
It can contain one or more Given steps, which are run before each scenario",
and there is "only one set of Background steps per Feature or Rule"
(cucumber.io/docs/gherkin/reference). A Scenario Outline "is run
once for each row in the Examples section beneath it (not counting the first
header row)", with <angle-bracket> placeholders substituted from the table
before steps are matched (cucumber.io/docs/gherkin/reference).
Feature: Checkout promotions
Background:
Given a logged-in customer with "BOOK-001" at $24.99 in their cart
Scenario: Valid promo code reduces the subtotal
When they apply promo code "WELCOME10"
Then the subtotal is $22.49
Scenario Outline: Promo code outcomes
When they apply promo code "<code>"
Then the subtotal is <subtotal>
And the message is "<message>"
Examples:
| code | subtotal | message |
| WELCOME10 | 22.49 | 10% off applied |
| EXPIRED | 24.99 | This code has expired |
| GIBBERISH | 24.99 | Unknown promo code |Three near-identical copy-pasted scenarios collapse into one outline plus a table a BA can extend without touching code.
The failure mode has a name. Feature-coupled step definitions are "step definitions that can't be reused across features or scenarios", and they lead to "an explosion of step definitions, code duplication, and high maintenance costs" (cucumber.io/docs/guides/anti-patterns). The fix: "organise your steps by domain concept" and use "domain-related names (rather than feature- or scenario-related names)" (cucumber.io/docs/guides/anti-patterns).
Concretely, one parameterised a logged-in customer with "{sku}" at ${price} in their cart serves every checkout feature. Ten scenario-specific variants of it
do not. Before writing a new step, grep the existing step definitions for the
domain noun; reuse beats authoring.
Also avoid conjunction steps that fold two actions into one line ("I have shades and a brand new Mustang"); split them across Gherkin's own conjunction keywords, because "you want to strive to keep your steps atomic as much as possible" (cucumber.io/docs/guides/anti-patterns).
Then steps with no observable outcome. "Then the user is happy" cannot
assert. Then describes "an expected outcome, or result" and its definition
should "use an assertion to compare the actual outcome to the expected
outcome" (cucumber.io/docs/gherkin/reference).And chains. Usually a symptom of imperative style; rewrite at
intent level before adding step definitions.Background. It runs before every scenario in the feature
(cucumber.io/docs/gherkin/reference), so setup only two of eight
scenarios need belongs in those scenarios instead.BDD's payoff is the shared understanding produced by the conversation. The Cucumber project is explicit that documentation and automated tests "are produced by a BDD team, you can think of them as nice side-effects. The real goal is valuable, working software, and the fastest way to get there is through conversations between the people who are involved in imagining and delivering that software." (cucumber.io/docs/bdd)
Read that as a cost test, which is practitioner judgment rather than a documented rule: if no non-technical stakeholder ever reads, reviews or writes a feature file, the translation layer is pure overhead. Gherkin parsing, step matching, the glue code and the extra indirection while debugging are real costs, paid to buy readability for an audience that is not reading. Write the tests directly in JUnit, pytest, NUnit or Mocha instead. Usually skip BDD for: internal libraries, SDKs, CLIs and infrastructure with no business-facing behaviour; teams where only engineers have ever opened a feature file; retrofitting Gherkin onto an existing UI automation suite (which reliably produces the imperative anti-pattern above with no discovery benefit); and spike work whose specification is not stable enough to formulate.
The honest signal that BDD is working: someone who does not write code has
edited a .feature file in the last month.
These go deeper on individual pieces, where also installed:
cucumber-testing,
behave-testing,
reqnroll-testing,
gherkin-from-stories,
manual-step-to-gherkin,
bdd-step-library-curator.