Resource library

QA How-To

BDD with Cucumber: A Practical Guide

Use BDD and Cucumber effectively with concrete discovery examples, readable Gherkin, maintainable step definitions, automation design, and team practices.

2,041 words | Article schema | FAQ schema | Breadcrumb schema

Overview

BDD is often reduced to writing Given, When, Then above an existing test script. That misses its main value. Behavior-driven development is a collaboration practice for discovering examples before implementation, clarifying rules in shared language, and carrying selected examples into executable documentation. Cucumber can automate those examples, but it cannot create the conversation that makes them useful or resolve ambiguous product decisions alone.

This guide follows a feature from discovery to maintainable automation. You will learn how to run an example workshop, write declarative Gherkin, use backgrounds, tables and scenario outlines judiciously, design step definitions, manage state, select the right automation boundary, and keep the suite fast and trustworthy. It also identifies common patterns that make Cucumber expensive without improving understanding. A shipping rule provides a concrete example throughout.

Separate BDD From the Cucumber Tool

BDD is the practice of exploring desired behavior through concrete examples and shared language. Cucumber is a runner that reads Gherkin feature files and connects steps to code. A team can practice BDD without Cucumber, and a team can use Cucumber without practicing BDD. The difference is whether examples drive shared understanding before code, or simply reformat tests after requirements are complete.

Use Cucumber when business-readable examples need to remain living documentation and product, engineering, and QA will review them. Do not use it automatically for low-level algorithms, exhaustive API combinations, or technical checks that no nondeveloper needs to read. Every abstraction layer has cost. Cucumber earns that cost when a meaningful business rule becomes clearer and more durable in domain language.

  • BDD discovers and clarifies behavior through conversation and examples.
  • Gherkin records selected examples in a structured shared language.
  • Cucumber executes those examples by matching steps to code.
  • Not every automated test belongs in a feature file.

Discover Rules With Example Mapping

Bring product, development, and testing perspectives together before implementation. Start with a story, identify business rules, add concrete examples for each rule, and capture unanswered questions. For a free-shipping story, one rule might say members receive free standard shipping on domestic orders over $40. Examples should expose boundaries: $39.99, exactly $40, an international address, an excluded oversized item, and a guest customer.

Keep the session narrow enough to finish, usually one story or rule cluster. Ask what outcome matters, which terms are ambiguous, what happens at boundaries, and which exception has priority when rules overlap. The result is not a giant test inventory. It is an agreed set of representative examples and explicit questions. Those examples inform acceptance criteria, implementation design, lower-level tests, and a small number of executable scenarios.

  • Story: the user capability under discussion
  • Rules: statements that determine behavior
  • Examples: specific inputs and expected outcomes
  • Questions: unresolved assumptions requiring a decision

Write Scenarios in Domain Language

A scenario should describe one observable behavior, not the sequence of clicks used to reach it. Compare `When the member submits an eligible domestic order` with `When I click the cart icon and click Checkout and type the postcode`. The first survives a redesign and keeps attention on the rule. UI details belong in step-definition or screen-object code when the UI is the chosen boundary.

Use Given for relevant preconditions, When for the triggering action, and Then for observable outcomes. And and But improve readability but do not change semantics. Keep scenarios short enough that a reader can identify the rule. Avoid conjunction steps that hide several actions, such as `When I register and log in and buy an item`. If the example needs many unrelated facts, the story may be too broad or the setup vocabulary may need a domain concept.

  • Name behavior and business state, not controls and implementation.
  • Keep one primary When and one coherent outcome per scenario.
  • Use stable domain terms consistently across features and product UI.
  • Give scenario titles that state the differentiating rule or example.

Use Gherkin Structures With Restraint

Feature descriptions explain the capability and business value. Background can hold a short precondition genuinely shared by every scenario, but a long background forces readers to scroll and remember hidden state. Prefer a meaningful step such as `Given Priya is an active member` over five technical setup steps. Rules can group scenarios under a business rule and make larger feature files easier to navigate.

Scenario Outline is useful when the same rule is demonstrated by several important examples: `Scenario Outline: Member shipping threshold Given an active member has a domestic order worth <total> When shipping options are calculated Then standard shipping costs <cost> Examples: | total | cost | | 39.99 | 4.99 | | 40.00 | 0.00 |`. Do not feed hundreds of combinations through Gherkin. Put exhaustive permutations in focused code-level tests.

  • Background contains only context shared by every scenario in scope.
  • Data tables express structured inputs or expectations, not database dumps.
  • Doc strings hold readable messages or payload examples when they add clarity.
  • Scenario outlines demonstrate a rule with a small, meaningful example set.

Design Step Definitions as a Thin Translation Layer

A step definition matches human-readable text, converts parameters, and delegates to domain or test-support code. Keep assertions in outcome steps and avoid embedding large browser scripts directly in regex callbacks. For example, `When('shipping is calculated', async function () { this.quote = await shippingApi.quote(this.order); });` translates the phrase while a reusable client performs the operation. This separation makes step text readable and code testable.

Prefer specific expressions such as `Given an active member has an order worth {float}` over broad patterns that match many phrases. Cucumber will report ambiguous matches, but duplicated near-synonyms still create a confusing vocabulary. Search existing steps before adding one. Organize definitions by domain capability, not by individual feature file, and refactor common concepts only when they truly mean the same thing.

  • Parse step parameters into domain types at the boundary.
  • Delegate to APIs, models, screen objects, or service clients.
  • Avoid steps that call other steps and obscure execution flow.
  • Keep a small vocabulary and remove duplicate phrasings.

Control Scenario State and Hooks

Each scenario should receive a fresh world or context object. Store only its created entities, responses, and helper clients there. Never use mutable globals that can leak across scenarios or parallel workers. A Given step should establish state directly through a supported API or builder when the setup journey is not itself under test. Return created IDs and tag data with the run identifier for reliable cleanup.

Use hooks for technical cross-cutting concerns such as browser startup, trace capture, database transaction boundaries, and cleanup. Do not hide business preconditions in a Before hook, because readers cannot see them in the scenario. Tagged hooks can configure a genuine technical need, such as starting a mobile driver for `@mobile`, but excessive tag-driven behavior makes execution difficult to reason about. Capture failure artifacts before teardown changes the state.

Choose the Fastest Useful Automation Boundary

Gherkin does not require browser automation. The free-shipping rule can execute against a domain service or API in milliseconds, while one separate journey proves the UI integrates with it. Driving every scenario through a browser makes examples slow, flaky, and expensive to diagnose. Choose the lowest boundary that still observes the behavior promised to the user or business with credible evidence.

A balanced suite may execute pricing rules through in-process services, account permissions through APIs, and a handful of purchase journeys through the packaged UI. Keep boundary choice out of feature wording so the same domain scenario can survive implementation changes. Still, avoid running one scenario at multiple layers without a clear reason, because duplicated assertions increase maintenance without adding distinct risk coverage.

  • Domain or component boundary for rules and calculations
  • API boundary for service contracts and workflows
  • UI boundary for critical integration and user interaction
  • Manual exploration for novelty, usability, and unanticipated behavior

Make Execution Parallel, Observable, and Selective

Independent scenarios can run in parallel when data and context are isolated. Provision one mutable aggregate per scenario and avoid feature-order assumptions. Publish a readable report that links each failed step to the underlying error, screenshot, trace, request ID, and application version. A business reader should see which example failed, while an engineer can reach technical evidence without decoding a stack of nested steps.

Use tags to describe stable categories such as `@smoke`, `@payments`, `@wip`, or an external requirement ID. Avoid tags for people or temporary execution hacks. The pull request pipeline can run a small smoke set, while the broader executable specification runs after merge. Do not use tag filters to make chronically failing scenarios disappear. Quarantine narrowly with an owner and expiration while keeping the result visible.

Recognize Cucumber Failure Patterns

The most common failure is imperative Gherkin that mirrors the interface. It changes with every redesign and provides no business explanation. Other warning signs include feature files generated from spreadsheets, enormous backgrounds, reusable steps that accept any wording, scenarios that verify several rules, technical hooks that create invisible business state, and a step catalog so large nobody can discover the right phrase.

A second failure is treating every stakeholder as a feature-file editor. Product partners should review examples and language, but engineers still own executable code quality. Apply code review, linting, formatting, and refactoring to feature files and definitions. Delete examples that no longer explain important behavior. Living documentation is alive because the team uses and maintains it, not because a report was published.

  • Rewrite click-by-click scenarios around rules and outcomes.
  • Move combinatorial data to lower-level parameterized tests.
  • Expose business setup in Given steps instead of hooks.
  • Merge duplicate vocabulary only when domain meaning matches.

Adopt BDD One Valuable Feature at a Time

Choose a feature with real rule ambiguity and stakeholders willing to join a short discovery session. Map rules and examples, automate only the examples that provide durable documentation, and measure whether questions were resolved before coding. Keep the initial stack simple: one runner, a fresh scenario context, API-based setup, concise reporting, and a small set of domain steps that everyone understands.

Review the feature after delivery. Can a product partner still understand it? Does each scenario reveal a rule? Is the execution boundary fast and dependable? Did examples prevent rework or improve review? Expand when the collaboration creates value, not because a target number of scenarios has been reached. BDD succeeds when shared examples improve decisions and remain trustworthy after the implementation evolves.

Frequently Asked Questions

What is BDD in Cucumber?

BDD is a collaboration and discovery practice that uses concrete examples to clarify behavior. Cucumber is a tool that executes selected examples written in Gherkin by connecting their steps to code. Using Cucumber alone does not guarantee a team is practicing BDD.

What is the difference between Given, When, and Then?

Given establishes relevant preconditions, When describes the behavior-triggering action, and Then states an observable outcome. They create a readable causal example and should use domain language rather than low-level implementation steps.

When should I use a Scenario Outline in Cucumber?

Use it when several concise input and outcome rows demonstrate the same business rule, especially meaningful boundaries. Move large combinatorial datasets to code-level parameterized tests where they are faster and easier to maintain.

Should Cucumber tests run through the user interface?

Not necessarily. Run a scenario at the fastest boundary that proves the described behavior, such as a domain service or API. Keep a smaller number of UI scenarios for risks that require the packaged interface and full integration.

How do you avoid duplicate Cucumber step definitions?

Search the existing vocabulary before adding a step, use specific Cucumber expressions, and organize definitions by domain capability. Refactor synonyms only when they represent the same business meaning, rather than forcing unrelated contexts into a generic step.

Is Cucumber suitable for every automation project?

No. It is most useful when cross-functional readers need executable examples of important business behavior. Low-level algorithms, exhaustive data combinations, and purely technical checks are usually clearer and cheaper in ordinary test code.

Related QAJobFit Guides