Acceptance Testing, BDD & Gherkin
Acceptance examples turn stakeholder rules into observable scenarios; BDD is the collaborative discovery process that produces them, and Gherkin is one syntax for executable examples. Good scenarios describe business behavior independently of UI procedure and run at the cheapest boundary that preserves the rule.
Acceptance testing asks whether a concrete example satisfies an agreed rule from the perspective of a stakeholder or external system. Behavior-driven development (BDD) is the conversation that discovers those rules and examples. Gherkin gives selected examples a structured, executable form. Installing Cucumber and writing Given/When/Then around existing test steps does not by itself create either acceptance or shared understanding.
Discovery comes before automation #
A productive conversation brings together people who understand the need, the implementation and the risks. Example Mapping captures four things:
flowchart TD S["Story / outcome"] --> R["Business rule"] R --> E1["Concrete example"] R --> E2["Boundary example"] R --> Q["Open question"]
- the story or outcome supplies purpose;
- rules summarize constraints;
- examples make each rule concrete and falsifiable;
- questions expose ambiguity rather than burying assumptions in code.
Examples are valuable even when never automated. They may reveal that two stakeholders mean different things by “active customer,” that a cutoff uses local rather than UTC time, or that a refund rule lacks an answer at the boundary. Automating before resolving the question merely encodes one person's guess.
Gherkin expresses context, event and outcome #
Feature: Card payment retry
Rule: A soft decline may be retried once
Scenario: Second authorization succeeds
Given the first authorization was softly declined
When the customer retries with the same card
Then the order is paid exactly once
And one successful authorization is recorded
Given establishes relevant context, When names the event or decision, and Then states an externally observable outcome. And and But improve reading; they do not add new phases. A Scenario Outline runs the same example shape for rows in an Examples table, but a large table can hide distinct rules that deserve names.
The keyword is not part of step matching in Cucumber, so identically worded Given and Then steps are a smell: context and outcome have different intent even if the implementation regex can match both.
Describe behavior, not interface choreography #
This scenario is coupled to one screen layout:
When I click the third row
And I enter "10" in field "amount"
And I press the blue button
Then I see text in div "result-2"
It says how to operate the current UI but not which business rule matters. Prefer:
When the account holder withdraws 10 EUR
Then the withdrawal is accepted
And the available balance is 15 EUR
Step definitions may drive an API, domain service or UI depending on the evidence needed. Keeping business language above that adapter allows the interface to change without rewriting the specification. It also prevents acceptance tests from becoming synonymous with slow browser tests.
Choose examples that partition the rule #
For a threshold, write examples below, at and above it. For permissions, cover a permitted actor, a denied actor and the boundary where authority changes. For time, cover before/at/after the cutoff and declare the time zone. For retries, distinguish transient from permanent failure and the point at which an external side effect may already have happened.
Avoid listing every combination in Gherkin. Use a few examples that explain the rule; use lower-level table/property tests for combinatorial coverage. An executable specification should remain readable enough for the people who own the rule to review it.
Keep step definitions thin and coherent #
A step definition translates domain language into an action on the system. It should not contain a second business implementation. Share domain-level vocabulary, not giant generic steps such as Given I call endpoint X with JSON Y that turn feature files into a verbose HTTP client.
Scenario state should be isolated. Each example creates its own world or unique entities and must not depend on a previous scenario. Hooks are appropriate for technical lifecycle, but hidden business setup in a global Before hook makes the scenario lie about its context.
Use tags for meaningful execution concerns such as a dependency or deployment stage, not to encode arbitrary suite order. Keep failures tied to the scenario rule and preserve the request, response and external identifiers needed to diagnose the broken boundary.
Acceptance is not a substitute for other tests #
Acceptance examples confirm selected rules. They do not exhaust input space, prove internal invariants, validate every database query, establish performance under load or detect all security failures. A healthy path is:
- discover rules and boundary examples collaboratively;
- automate the examples at the smallest credible public boundary;
- drive implementation with focused unit and integration tests;
- retain a small browser journey only when UI composition is part of acceptance;
- add specialized performance, resilience and security evidence for their own risks.
Failure modes #
- BDD as syntax: no stakeholder conversation, only feature files written by testers after implementation.
- Procedural scenarios: steps mirror clicks and fields, so UI redesign rewrites the specification.
- One scenario per requirement sentence: ambiguity remains because no concrete boundary example was chosen.
- Automation-owned language: stakeholders cannot review the vocabulary or expected outcomes.
- Database
Then: internal state passes while user-visible behavior is wrong. - Feature-file duplication: the same rule is restated across scenarios with inconsistent wording.
- Everything through Cucumber: low-level algorithm examples become slower and harder to diagnose without adding communicative value.
The durable artifact is not the .feature file. It is the shared, testable understanding of a rule. Gherkin is valuable when it preserves that understanding close to the executable system.