BDD and Gherkin
Behaviour-driven development (BDD) is a way of working in which the business, development and testing perspectives agree how a feature should behave by discussing concrete examples, write the agreed examples in a structured plain language called Gherkin, and then automate them so that they check the system. The ISTQB Foundation syllabus describes BDD as expressing the desired behaviour of an application with test cases written in a simple form of natural language that stakeholders can understand, usually in the Given/When/Then format, which are then automatically translated into executable tests.
Take a discount code at checkout. In a short conversation the team agrees the rule "SAVE10 takes 10% off a subtotal of 200.00 SAR or more" and two examples: a subtotal of 200.00 SAR pays 180.00, and 199.99 SAR keeps its price. Each example becomes a Gherkin scenario in a feature file, and step definitions connect every line of the scenario to automation code. The same text is then the specification the business approved, a test that runs on every build, and documentation of the rule.
Why it matters
Many costly defects in business features are not coding slips but misunderstandings: the developer read "200.00 or more" as "more than 200.00", or nobody decided what an expired code should do. BDD goes after that cost at its source. A conversation about concrete examples before any code is written exposes different readings and missing rules while they are cheap to fix, which makes it one of the earliest forms of static testing a team has.
For a tester, BDD moves the work forward: instead of finding gaps after the build, you help shape the examples that define the feature, and the same scenarios later serve as acceptance tests and regression tests. Reports speak the language of the business, so a failed scenario says which rule broke. The approach has a cost, though: a layer of step definitions to maintain. Teams that adopt the tool without the conversation pay that cost and get little back.
BDD starts with a conversation: the three amigos
Cucumber, one of the best-known BDD tools, describes day-to-day BDD as three practices repeated for every small change. Discovery: the team talks through concrete examples of the new behaviour and agrees on the details. Formulation: the agreed examples are written down as Gherkin scenarios, and the people who agreed them check that the wording still says what they meant. Automation: the scenarios are connected to code so that they run against the system, ideally before the feature is built, to guide development. In short: what the system could do, what it should do, and what it actually does.
Discovery usually happens in a short meeting per story during backlog refinement, before development starts. Many teams call it the "three amigos", because it brings together three perspectives: the business (a product owner or business analyst, who owns the problem and the rules), development (who knows how it could be built and what is technically hard) and testing (who asks what could go wrong and which examples would show it). It is the three perspectives that matter, not the head count: a designer or a second developer may join.
A popular way to run the meeting is example mapping, described in Cucumber’s BDD documentation. Coloured cards build a map on the table: a yellow card for the story, a blue card for each rule, green cards under a rule for the examples that illustrate it, and red cards for questions nobody in the room can answer. The map shows readiness at a glance: many red cards mean the story is not ready to build, and many blue cards usually mean it is too big and should be split. Here is the map for story SHOP-31, "Apply a discount code at checkout".
- Story (yellow): SHOP-31, apply a discount code at checkout.
- Rule 1 (blue): SAVE10 takes 10% off a subtotal of 200.00 SAR or more. Examples (green): 250.00 pays 225.00; 200.00 pays 180.00; 199.99 is refused and pays 199.99.
- Rule 2 (blue): an expired code is refused. Example (green): the code ended on 31 March 2027 and is applied on 1 April 2027, so the message is "This code has expired".
- Rule 3 (blue): one code per order. Example (green): SAVE10 is applied, then WELCOME5 is entered; the result waits on a red card.
- Questions (red): is the minimum checked before or after the delivery fee? Does a second code replace the first or get refused? What happens when the customer removes items and the subtotal falls below 200.00 after the code was applied?
Try it: User Story → Test Scenarios Try it: Boundary Value Generator Try it: Decision Table Generator
Gherkin syntax: the parts of a feature file
Gherkin is the plain-language format that BDD tools read. Scenarios live in text files with the .feature extension, and every line that is not blank starts with a keyword, except free-text descriptions. You already know the step keywords Given, When, Then, And and But, and the Scenario Outline with its Examples table, from the lesson on acceptance criteria. A complete feature file adds structure around them.
After the product owner answered the first red card (the minimum applies to the subtotal, before the delivery fee), the team formulated rules 1 and 2 of SHOP-31 as the file below. Each blue card became a Rule and each green card a Scenario. The Gherkin reference recommends three to five steps per scenario; with more, the example stops reading as a specification.
Feature: one per file. Its name and the free-text description under it say what the feature is for; the description is not executed.Rule: groups the scenarios that illustrate one business rule, like a blue card with its green cards.Scenario, or its synonymExample: one concrete example of one behaviour.Background: Given steps shared by every scenario in the file or the rule. It runs before each scenario, after anyBeforehooks (Cucumber’s setup hooks, likebeforeEachin other runners). Keep it short and use it only for context a reader needs to understand the scenarios.- Data table: rows between
|pipes under a step, passed to that step’s code as one argument. Doc string: multi-line text between two"""lines, for an email body or a JSON payload. - Tags such as
@smokeor@SHOP-31label a feature, rule, scenario or examples table so that you can choose what to run. Tags are inherited: a tag on the feature applies to every scenario in it. - Comments start with
#at the beginning of a line. A# language:header on the first line switches the keywords to another spoken language.
# Story SHOP-31. Rules agreed in the example-mapping session.
@checkout
Feature: Discount codes at checkout
A customer can enter one discount code per order.
The discount is taken from the subtotal, before the delivery fee.
Background:
Given these discount codes exist:
| code | percent | min_subtotal | valid_until |
| SAVE10 | 10 | 200.00 | 2027-03-31 |
Rule: A code applies only from its minimum subtotal
Scenario: A subtotal at the minimum gets the discount
Given Huda has a cart with a subtotal of 200.00 SAR
When she applies the code "SAVE10"
Then the amount to pay before delivery is 180.00 SAR
Scenario: A subtotal just under the minimum keeps its price
Given Huda has a cart with a subtotal of 199.99 SAR
When she applies the code "SAVE10"
Then the code is refused with "Spend 200.00 SAR to use this code"
And the amount to pay before delivery is 199.99 SAR
Rule: An expired code is refused
@smoke
Scenario: A code used the day after its end date
Given today is 1 April 2027
And Huda has a cart with a subtotal of 250.00 SAR
When she applies the code "SAVE10"
Then the code is refused with "This code has expired"Rule 1 of story SHOP-31 in a feature file (excerpt)
Part 1:@checkout Part 2:Feature: Discount codes at checkout Part 3: A customer can enter one discount code per order. Part 4: Background: Given these discount codes exist: Part 5: | code | percent | min_subtotal | valid_until | | SAVE10 | 10 | 200.00 | 2027-03-31 | Part 6: Rule: A code applies only from its minimum subtotal Part 7: Scenario: A subtotal at the minimum gets the discount Part 8: Given Huda has a cart with a subtotal of 200.00 SAR Part 9: When she applies the code "SAVE10" Part 10: Then the amount to pay before delivery is 180.00 SAR
- 4–5Runs before each scenario
- 8–10Steps: each line is matched to a step definition
- 1TagLabels the whole feature; every scenario inherits it, so one run can select them all.
- 2FeatureOne per file: the capability the scenarios describe.
- 3DescriptionFree text for readers; the runner does not execute it.
- 4BackgroundContext shared by every scenario; it runs before each one, after any
Beforehooks. - 5Data tableRows passed to the step’s code as one argument: the discount code the scenarios rely on.
- 6RuleOne business rule, the blue card of the example map; its scenarios are the green cards.
- 7ScenarioOne example of one behaviour; the title states the outcome.
- 8Given: the contextPuts the system in a known state;
{word}and{float}capture Huda and 200.00. - 9When: the actionThe one action under test;
{string}captures SAVE10. - 10Then: the outcomeAn observable outcome; its step definition asserts that the page shows 180.00.
Good and bad scenarios
The most common way BDD goes wrong is a feature file that is really a test script: every click, field and URL written out in Given/When/Then. This imperative style breaks with every redesign, and it buries the rule under user-interface detail. The declarative style says what happens in the language of the business and leaves the how to the automation code. Compare two versions of the same example.
Nothing in the first version says why 180.00 is the right answer: the subtotal of 200.00 SAR, the one fact the rule depends on, is hidden in the price of a book. Its last step checks a database table that no stakeholder can see, and its steps glue several actions together with "and", so they cannot be reused. The product owner can read and approve the second version, and it survives a redesign of the checkout page unchanged.
- One behaviour per scenario, with a title that states it: "A subtotal just under the minimum keeps its price", not "Discount test 2".
- Declarative steps in business words; the clicks belong in the step definitions and page objects.
- Only the data that changes the result. Every value in the scenario should matter to the rule, and nothing the rule depends on should be hidden.
- An outcome a user or stakeholder can observe in every Then, specific enough to fail: "Then I should see 180.00" does not say where.
- Independent, repeatable scenarios: each one sets up its own context and passes or fails alone, in any order and on any day. The rule 1 scenarios do not mention a date, so their automation must fix the shop’s clock, for example in a
Beforehook; otherwise they start failing the day SAVE10 expires. - One phrase per idea across the project, such as "subtotal" everywhere rather than "cart total" in one file and "basket amount" in another, so that steps reuse the same definitions.
# Imperative: a test script dressed as a scenario
Scenario: Discount test 2
Given I open "https://shop.example/login"
And I type "huda@example.com" in "email" and click "Sign in"
And I click "Add to cart" next to "Desert Stars"
And I click "Checkout"
When I type "SAVE10" in "code" and click "Apply"
Then I should see "180.00"
And the discounts table has a row for this order
# Declarative: one behaviour in the language of the business
Scenario: A subtotal at the minimum gets the discount
Given Huda has a cart with a subtotal of 200.00 SAR
When she applies the code "SAVE10"
Then the amount to pay before delivery is 180.00 SARHow scenarios connect to automation
A BDD runner such as Cucumber reads the feature files and, for every step, looks for a step definition: a function registered with a pattern that the step text must match. The pattern is usually a Cucumber Expression, in which parameters such as {int}, {float}, {word} and {string} capture values from the text and pass them to the function as arguments; regular expressions work as well. The keyword is not part of the match: a Given step and a Then step with the same text call the same definition, so the same text cannot mean two different things in two places.
Step definitions are glue code and should stay thin. They translate a business step into calls to the automation layer you met in the lessons on the page object model and test automation architecture: an API client prepares the data for a Given, a page object performs the When as a user would, and the Then reads the result and makes the assertion that compares the actual outcome with the expected one. Values that one step produces for a later step, such as the cart the Given created, are kept in a context object. In Cucumber for JavaScript this is the World, available as this inside a step function (so steps use function, not arrow functions, which have no this of their own) and created fresh for every scenario, which keeps scenarios independent; the API client and page objects are usually created there too, so every step can reach them.
Every step ends with a status: passed, failed, skipped (steps after a failed, pending or undefined step are not run), pending (a definition that is not finished yet), undefined (no definition matches; Cucumber prints a code snippet to start from) or ambiguous (more than one definition matches). The report lists scenarios and steps in the words of the feature file, so a failed Then step names the rule that broke. Tags select what runs, for example npx cucumber-js --tags "@smoke and not @wip" in a pipeline.
import { Given, When, Then } from "@cucumber/cucumber";
import assert from "node:assert/strict";
// Matches the Background step; the data table arrives as the last argument
Given("these discount codes exist:", async function (table) {
for (const row of table.hashes()) await this.api.createCode(row);
});
// Matches: Given Huda has a cart with a subtotal of 200.00 SAR
Given("{word} has a cart with a subtotal of {float} SAR",
async function (customer, subtotal) {
this.cart = await this.api.createCart(customer, subtotal);
});
// Matches: When she applies the code "SAVE10"
When("she applies the code {string}", async function (code) {
await this.checkout.open(this.cart);
await this.checkout.applyCode(code);
});
// Matches both: Then the amount to pay ... and: And the amount to pay ...
Then("the amount to pay before delivery is {float} SAR",
async function (expected) {
assert.equal(await this.checkout.amountBeforeDelivery(), expected);
});
Then("the code is refused with {string}", async function (message) {
assert.equal(await this.checkout.codeMessage(), message);
});- Cucumber runner reads the feature file
- Step definitions glue code
- API client and page object automation layer
- The shop system under test
- 1 Cucumber runner internally: Parses the file; runs hooks and the Background, then the scenario
npx cucumber-js --tags @checkout - 2 From Cucumber runner To Step definitions: Given step matches its expression
{word}= Huda,{float}= 200 - 3 From Step definitions To API client and page object: Creates the cart through the API client
api.createCart(customer, subtotal) - 4 From API client and page object To The shop: Prepares the data through the API, not the screens
- 5 From Cucumber runner To Step definitions: When step:
{string}captures SAVE10 - 6 From Step definitions To API client and page object: Applies the code through the page object
checkout.applyCode(code) - 7 From API client and page object To The shop: Types the code and selects Apply, as a user would
- 8 From Cucumber runner To Step definitions: Then step: one definition serves every Then or And with this text
- 9 From Step definitions To API client and page object: Asks the page object for the amount before delivery
checkout.amountBeforeDelivery() - 10 From API client and page object To Step definitions: Returns 180, read from the checkout page reply
- 11 Step definitions internally: Assertion: actual 180 equals expected 180
assert.equal(actual, expected)Pass
Outcome: The scenario passed. A failed assertion marks its step failed, and the steps after it are skipped. Pass
Common mistakes
- Treating BDD as a tool choice: installing Cucumber and having testers write feature files alone after development. There is no discovery and no shared understanding, only an extra layer to maintain.
- Writing imperative, user-interface-level scenarios full of clicks, field names and URLs, which break with every redesign and hide the rule.
- Conjunction steps that join several actions with "and", and scenarios that test several behaviours with repeated When and Then pairs. They cannot be reused, and when they fail nobody knows why.
- Scenarios that depend on each other or on data left behind by an earlier scenario. Each scenario must set up its own context.
- Turning every data combination into rows of an
Examplestable until there are dozens of user-interface tests. Move the combinations down to unit or API tests and keep a few examples that explain the rule. - Letting the step definition layer sprawl: near-duplicate phrasings such as "the cart total is" and "the basket amount is", each with its own definition, or logic inside step definitions instead of in page objects and API clients.
The QA perspective
In discovery, the tester is the amigo who asks "what if?". You bring the boundary values (199.99 and 200.00), the negative examples and the questions about state and timing, and you make sure every rule has an example that shows it refusing, not only accepting. Turn every unanswered question into a red card with an owner instead of guessing. In formulation, review the scenarios as you would review requirements: can the product owner read and agree with each one, and is each one a single behaviour with an observable outcome?
In automation, pair with the developer on the step definitions, keep a shared list of approved step phrases, and tag each scenario with its story id for traceability. When a scenario fails in the pipeline, the failed step tells you which rule broke; debug it as in the lesson on automation reporting. When a rule changes, change the scenario first and the code second: living documentation stays alive only while the scenarios stay true.
Practice exercise
Turn a class-booking script into BDD scenarios
A gym app has the story below, notes from the product owner, and a draft feature file that a team member wrote after the feature was built. Treat the notes as your discovery input: map the rules, examples and open questions, then rewrite the feature file and plan its step definitions.
Where a value or a message is not given, propose one and mark it as a question for the product owner instead of deciding it yourself.
GYM-12 Book a place in a fitness class
As a member, I want to book a place in a class,
so that I am sure to have a spot.
Notes from the product owner: bookings open 7 days before the class starts.
A class has 20 places. When it is full, members can join a waiting list.
Draft feature file:
Feature: Booking
Scenario: Test booking
Given I am on the login page
And I enter my email and password and click "Log in"
And I click "Classes" and choose "Spinning, Monday 18:00"
When I click "Book"
Then I see a green message
When I click "Book" for "Yoga, Tuesday 09:00"
Then the bookings table has 2 rows for member 1187- List every problem in the draft scenario.
- Build an example map: the rules, examples with boundary values for each rule, and the questions you would ask.
- Rewrite the feature file with one
Ruleper business rule and short declarative scenarios. - Write the Cucumber Expression for each step definition and note which steps share one.
Show the model answer
Problems: the title names no behaviour; sign-in, clicks and menu names are imperative detail; two steps join actions with "and"; two When and Then pairs test two behaviours; "a green message" is not a specific outcome; the last Then checks a database table nobody can see; and there is no rule, refusal or boundary.
Example map. Rule 1: bookings open 7 days before the class starts; examples: booking exactly 7 days before the start is accepted, booking 7 days and 1 minute before is refused. Rule 2: a class has 20 places; examples: with 19 places booked the booking is accepted and no place is left, with 20 booked the booking is refused and the waiting list is offered. Rule 3: a member can join the waiting list of a full class; example: Sami joins and is number 1 on the list. Questions: are the 7 days counted to the minute or in calendar days? Can a member book the same class twice? When someone cancels, is the first person on the waiting list booked automatically or only notified?
Rewritten, for rule 2: Feature: Booking a place in a class. Rule: A class has 20 places. Scenario: The last place can be booked, with Given the class "Spinning, Monday 18:00" has 19 of 20 places booked; When Sami books "Spinning, Monday 18:00"; Then the booking is confirmed; And the class has 0 places left. Scenario: A full class offers the waiting list, with Given the class "Spinning, Monday 18:00" has 20 of 20 places booked; When Sami books "Spinning, Monday 18:00"; Then the booking is refused with "This class is full"; And Sami is offered the waiting list. Rule 1 is about a moment in time, so its boundary needs a clock: Scenario: Booking opens exactly 7 days before the class, with Given the class "Yoga, Tuesday 09:00" starts at "2026-11-10 09:00"; And the time is "2026-11-03 09:00"; When Sami books "Yoga, Tuesday 09:00"; Then the booking is confirmed. A second scenario sets the time to "2026-11-03 08:59" and expects Then the booking is refused with "Booking opens 7 days before the class". Rule 3 gets its own Rule block in the same way.
Step definitions as Cucumber Expressions: the class {string} has {int} of {int} places booked; {word} books {string}; the booking is confirmed; the class has {int} places left; the booking is refused with {string}; {word} is offered the waiting list; and for rule 1, the class {string} starts at {string} and the time is {string}. Both rule 2 scenarios reuse the same Given and When definitions with different values, and rule 1 reuses {word} books {string}, the booking is confirmed and the booking is refused with {string} from rule 2. The messages "This class is full" and "Booking opens 7 days before the class" are proposals for the product owner to confirm.
Frequently asked questions
Is BDD the same as writing tests in Gherkin?
No. BDD is the collaboration: discovering behaviour through concrete examples with the business, development and testing perspectives, then formulating and automating those examples. Gherkin is the format the examples are written in. Feature files written by testers alone after the code exists are automated test scripts with an extra layer: they bring the maintenance cost of BDD without its main benefit, the shared understanding.
Who writes the step definitions?
It depends on the team. Developers or automation engineers often write them, sometimes while building the feature, and testers write or review the scenarios and pair on the automation. What matters is that the scenario text is agreed with the business and that the step definitions stay thin, calling the same page objects and API clients as the rest of the test code.
Should every test be a Gherkin scenario?
No. Use scenarios for behaviour the business cares about and wants to read, with a few examples per rule. Detailed input combinations, technical checks and most negative cases are cheaper as unit or API tests without the Gherkin layer, in line with the test automation pyramid.
Can Gherkin be written in Arabic or Hindi?
Yes. A # language: header on the first line of a feature file switches the keywords to another spoken language, and Arabic and Hindi are among the translations. Many teams instead keep the English keywords and write the step text in their own language. Either way, the step definitions must match the step text exactly, so agree on the wording once and reuse it.
References
- Gherkin Reference — CucumberThe Gherkin syntax for executable specifications: Feature, Rule, Scenario, Given/When/Then steps, Background, Scenario Outline and Examples.
- Behaviour-Driven Development — CucumberBehaviour-Driven Development as three practices — discovery, formulation and automation — built on concrete examples that business and technical people agree on.
- ISTQB Certified Tester Foundation Level (CTFL) v4.0The foundation syllabus of software testing: test levels and types, test design techniques, test management and tools.
Practise with Mutqan tools
Generate Given/When/Then acceptance criteria and a definition-of-done checklist from a user story.
Derive end-to-end test scenarios from a user story, including alternate and exception flows.
Generate boundary values (min-1, min, min+1, nominal, max-1, max, max+1) for numeric and length constraints.
Build decision tables from conditions and actions, generate all rule combinations and collapse impossible ones.
Test regular expressions live with match highlighting, capture groups and replace preview.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.