
BDD Scenarios: 10 Best Practices and a Feature File Template
Good BDD scenarios describe one behavior each, in business language, using Given-When-Then with about 3-5 steps. Organize them as one feature per .feature file, grouped in folders by business domain, with Rule blocks for business rules and tags for selecting what runs. Below is a copy-paste BDD template, followed by ten practices for keeping features and scenarios readable as the suite grows.
The syntax here is Gherkin as documented in the Cucumber Gherkin reference. It applies to Cucumber (JavaScript, Java, Ruby), Reqnroll/SpecFlow, Behave, pytest-bdd and other Gherkin-based tools.
A BDD feature file template
@billing
Feature: Subscription upgrades
Customers on a paid plan can move to a higher plan at any time.
They pay the prorated difference immediately.
Background:
Given Ana has an active "Starter" subscription billed monthly
Rule: Upgrades are charged the prorated difference
@smoke
Scenario: Upgrade halfway through the billing period
Given Ana is 15 days into a 30-day billing period
When she upgrades to the "Pro" plan
Then she is charged half the price difference
And her plan is "Pro" immediately
Scenario Outline: Proration at different points in the period
Given Ana is <days> days into a 30-day billing period
When she upgrades to the "Pro" plan
Then she is charged <percent>% of the price difference
Examples:
| days | percent |
| 0 | 100 |
| 15 | 50 |
| 29 | 3 |
Rule: Downgrades take effect at the next renewal
Scenario: Downgrade keeps current features until renewal
When Ana downgrades to the "Free" plan
Then she keeps "Starter" features until her renewal date
And she is not charged or refunded today
What each part does:
Featurenames the capability. The free text below it explains why it exists. A.featurefile can only contain oneFeature.BackgroundholdsGivensteps shared by every scenario in the Feature (or in aRule, if you put it inside one).Rule(Gherkin 6 and later) groups the scenarios that illustrate one business rule.Scenario(or its synonymExample) is one concrete example of the rule.Scenario Outline+Examplesrun the same steps once per table row, filling in<placeholders>.- Tags (
@billing,@smoke) let you filter runs and attach hooks. They're inherited, so@billingapplies to every scenario in the file.
Folder structure
Organize by business domain, not by sprint, ticket or UI page:
features/
billing/
subscription-upgrades.feature
invoices.feature
accounts/
sign-up.feature
password-reset.feature
step_definitions/
billing.steps.js
accounts.steps.js
support/
world.js
hooks.js
Cucumber.js finds features/**/*.{feature,feature.md} by default and loads support code from features/**/*.@(js|cjs|mjs) when your features live under features/ (configuration docs). So this layout needs no configuration.
10 best practices for BDD features and scenarios
1. One feature per file, named after the capability
Name files after what the system does for the user: password-reset.feature, not JIRA-4812.feature or test3.feature. Ticket numbers belong in tags (@JIRA-4812) if you need traceability. A product owner should be able to find the rules for password resets by browsing the folder.
2. Group features by business domain
Folders mirror how the business talks about the product: billing, accounts, search, notifications. Avoid splitting by test type (ui/, api/). The same behavior may be checked at several layers, and the domain is what stays stable.
3. Use Rule to show the business rules
Before Rule existed, long feature files were a flat list of scenarios, and readers had to guess which rule each one illustrated. Put the rule in plain words after Rule: and the examples under it. If you can't state the rule in one sentence, you probably have two rules.
4. Write declarative steps, not UI scripts
Describe what the user achieves, not which buttons they press. Cucumber's own guide on writing better Gherkin gives the test: if the wording would have to change when the implementation changes, rewrite it.
# Imperative: breaks when the UI changes
When I go to "/login"
And I type "ana@example.com" into "Email"
And I type "secret" into "Password"
And I click "Sign in"
# Declarative: survives a redesign
When Ana signs in
The click-by-click detail moves into the step definition, where developers can change it in one place.
5. Given-When-Then, with 3-5 steps
The Gherkin reference recommends 3-5 steps per example. Use each keyword for its job:
- Given puts the system in a known state. Avoid describing user interaction here.
- When is the one action or event under test.
- Then checks an outcome the user could observe, not a database row or an internal flag.
A scenario with several Whens usually tests several behaviors. Split it.
6. One behavior per scenario, and keep scenarios independent
Each scenario should pass or fail for one reason, and run in any order. Never rely on data a previous scenario created. Set up what you need in Given steps or a Before hook, and clean up after. Independent scenarios can run in parallel and can be run one at a time while debugging.
7. Keep Background short and vivid
Only use Background for Given steps that every scenario in the file (or rule) really needs. Cucumber's advice: keep it short, avoid complicated setup the reader doesn't need to know, and use vivid names ("Ana has a Starter plan") instead of "User A". If the Background scrolls off the screen, readers forget it's there.
8. Use Scenario Outlines for data, not for different behaviors
Outlines are for the same behavior with different inputs: boundary values, currencies, roles. If rows need different Then wording or different steps, they're different behaviors and should be separate scenarios. Keep tables small. Five meaningful rows beat fifty generated ones. See BDD scenario outline tips and examples for more.
9. Use tags for selection and hooks, with a small fixed vocabulary
Agree on a short list, for example @smoke, @wip, @slow, plus one tag per domain, and document it. Tags can sit on Feature, Rule, Scenario, Scenario Outline and Examples, and are inherited downward. They can't go on Background or steps. Run subsets with tag expressions:
npx cucumber-js --tags "@smoke and not @wip"
npx cucumber-js --tags "(@billing or @accounts) and not @slow"
Hooks can target tags too:
// features/support/hooks.js
import { Before, After } from '@cucumber/cucumber';
Before({ tags: '@billing' }, async function () {
await this.resetBillingSandbox();
});
After(async function () {
await this.cleanup();
});
10. Organize step definitions by domain concept, not by feature file
Cucumber's step organization guide recommends one step-definition file per major domain object (billing, accounts, authentication), not one per feature. Feature-coupled step files lead to duplicates like "Ana signs in" defined three times with three slightly different implementations. Remember that Cucumber matches steps by their text and ignores the keyword. Given Ana signs in and When Ana signs in are the same step, so you can't define both.
// features/step_definitions/billing.steps.js
import { Given, When, Then } from '@cucumber/cucumber';
import assert from 'node:assert/strict';
Given('Ana is {int} days into a {int}-day billing period', function (days, length) {
this.clock.setDayOfPeriod(days, length);
});
When('she upgrades to the {string} plan', async function (plan) {
this.charge = await this.billing.upgrade(this.customer, plan);
});
Then('she is charged {int}% of the price difference', function (percent) {
assert.equal(this.charge.percentOfDifference, percent);
});
Keep the suite healthy
- Review features with the people who own the rules. BDD scenarios are meant to be read by product owners and testers, not just run by CI. A short review when a rule changes catches outdated scenarios early.
- Delete scenarios that no longer describe real behavior. A failing scenario nobody understands is worse than none.
- Watch the run time. Tag slow end-to-end scenarios and run them less often. Push most checks down to API or unit level, and keep a few UI scenarios for the critical paths.
New to the tooling? Start with Cucumber for beginners, or browse testing tools on DevHunt.
FAQ
What is a BDD scenario?
A BDD scenario is one concrete example of how the system should behave, written in Gherkin as Given (starting state), When (action) and Then (expected outcome). Tools like Cucumber map each step to code, so the scenario is both documentation and an automated test.
How many steps should a BDD scenario have?
The Cucumber Gherkin reference recommends 3-5 steps per example. If a scenario needs more, it's usually testing several behaviors or describing UI clicks instead of behavior.
What's the difference between BDD and TDD?
TDD is a developer practice: write a failing unit test, make it pass, refactor. BDD builds on it by describing behavior in business language, as examples agreed with product and QA, often in Gherkin. Teams commonly use both: BDD scenarios for features, TDD for the code underneath.
Should I use Scenario or Example?
They're synonyms in Gherkin. Example reads naturally under a Rule, and Scenario is more common in older suites. Pick one per project and stick to it.
What is a BDD framework?
It's the tool that parses Gherkin feature files and runs the matching step definitions. Common options are Cucumber (JavaScript, Java, Ruby and more), Reqnroll (the open-source successor to SpecFlow for .NET), Behave and pytest-bdd for Python, and playwright-bdd for Playwright.