top of page

Behavior-Driven Development (BDD) Without Cargo Cult

  • Shawn West
  • Mar 26
  • 11 min read

Updated: Aug 10

A team I watched adopt BDD did everything the tutorials said. They installed Cucumber, restructured their test folders into features/, and rewrote existing tests as Gherkin — Given a logged-in user, When they submit the form, Then they see a confirmation. Within a quarter they had roughly four hundred lines of Gherkin describing behavior a developer had already tested in plain code.

The product manager never opened those files. Not once. The scenarios lived in the repository, written in the developers' words, reviewed in developer pull requests. Meanwhile, writing a test now took two passes: the Gherkin sentence, and the step-definition "glue" code underneath that made the sentence actually run. Test-writing time roughly doubled. Six months in, the retro produced a tidy verdict: "BDD doesn't work for us."

They were half right. What they built didn't work — but what they built wasn't BDD. It was the airport built by a cargo cult: the runway, the control tower, the bamboo headset, assembled in the belief that the form summons the cargo. The islanders copied the airfield because they had seen airplanes land near one, and they had the causation backwards. The airport does not cause the airplane, and Gherkin syntax does not cause shared understanding. This piece is about telling the airplane from the airport, because the two look nearly identical in a repository and cost wildly different amounts to run.

Why writing Gherkin feels like doing BDD

The mistake is easy to make because BDD's most visible artifact is its syntax, and syntax is the part you can copy in an afternoon. Every "getting started with Cucumber" guide opens with Given / When / Then: install the runner, write a feature file, wire the steps, watch it go green. It feels like adoption — you can see the folder fill up.

What is invisible in that flow is the thing BDD is actually for. The Given / When / Then format was never the point; it is the packaging for the point. The point is a conversation, held before the code exists, in which the people who understand the business and the people who will build the system construct concrete examples of the desired behavior together — and discover, while it is cheap, that they disagree about what the system should do.

Skip that conversation and you still get green feature files — you just get them by translating a developer's existing mental model into stilted English, reviewing it among developers, and paying the two-language tax forever. The syntax is present; the practice is absent. That is the default outcome, not a rare failure, because the syntax is the part the tools hand you and the conversation is the part they cannot.

Runnable takeaway: check the commit history on three scenario files. If every author and reviewer is an engineer, you have the airport — the practice leaves fingerprints from outside engineering; the cargo cult does not.

What BDD actually is: examples before code

Strip away the tooling and BDD is one claim: the cheapest place to catch a misunderstanding about what to build is a concrete example, discussed before anyone writes code.

Requirements written as abstract rules hide disagreement. "Customers can get a refund within the return window" reads as settled, and everyone nods — which is the danger, because abstract language lets three people hold three definitions of "return window" and never collide. A concrete example forces the collision. "Bought on the 1st, policy says 30 days, they request a refund on the 31st at 11pm — refund or no refund?" cannot be answered by nodding. Someone has to say a number, and the moment they do, the person who had a different number speaks up.

That is the whole mechanism: examples are a disagreement-detector. BDD is the discipline of running that detector before the code, with business and engineering in the same room, then — as a downstream convenience, not the goal — capturing the agreed examples in a runnable format so they don't rot.

The order matters, and it is the order teams reverse: collaboration → concrete examples → shared agreement → (optionally) automated tests. The cargo cult runs it automated tests → in example format → by developers → alone. Same artifacts on disk, opposite value.

Runnable takeaway: for your next contested feature, write down one concrete example with real values before the design doc is signed off, and read it aloud to a non-engineer. If they correct a value, the practice just paid for itself before a line of code existed.

A Three Amigos session, and the bug it caught

(Developed example — composite scenario.)

Picture a subscription product with a rule everyone treats as obvious: "Refund a cancellation if the customer cancels within 14 days." One sentence in the PRD, sailed through review, engineering ready to build.

Before they did, the three amigos sat down — the product manager who owns the policy, the engineer who will build it, the tester who will try to break it. Their job for thirty minutes was not to design anything; it was to invent examples and agree on the answer for each. (This is the requirements-side skill covered in 'Acceptance Criteria the Given-When-Then Way, Done Right'; here we watch it catch a defect live.)

They started with the easy one and it went fine:

Scenario: Cancellation clearly inside the window
  Given a customer subscribed on March 1
  When they cancel on March 10
  Then they receive a full refund

Then the tester asked the awkward question: "What does within 14 days count from — the day they subscribed, or the start of the current billing period?" Silence. The PM: "from when they subscribed, obviously." The engineer: "the data I have is billing-period start; same thing for month one, but not after a renewal."

They wrote the example that exposed it:

Scenario: Cancellation after a renewal
  Given a customer subscribed on January 1
  And their subscription renewed on July 1
  When they cancel on July 10
  Then they receive a full refund   # ...do they?

The PM's face changed. To her, "within 14 days" had always meant within 14 days of a charge — a renewing customer who cancels ten days after being billed for a year they won't use should absolutely be refunded. To the engineer, "cancels within 14 days" meant fourteen days from the original signup, which here was six months ago. The one-sentence rule was true for both and meant opposite things. A renewing customer would be refunded or not depending on which developer read the ticket.

They added a third example to pin the boundary the PM actually intended:

Scenario: Renewal cancellation outside the window
  Given a customer whose subscription renewed on July 1
  When they cancel on July 20
  Then they are not refunded
  And their access continues until the period ends

The defect they caught was not a code bug. It was a requirement bug — an ambiguity that would have shipped as a plausible, tested, green feature and surfaced weeks later as furious renewing customers and a policy support couldn't explain. It cost thirty minutes and three examples. Found after code, it costs a re-scoping, a data-model question ("do we even store the last-charge date accessibly?"), a migration, and a trust hit with paying customers.

Now run the same feature as the cargo cult. A developer reads "refund within 14 days," writes the happy-path scenario, writes the glue, sees green, merges. The file says Given a customer subscribed on March 1 / When they cancel on March 10 / Then a full refund. Syntactically flawless BDD. The PM never sees it. The renewal ambiguity is still in there, undetected, because no example ever forced the collision — the one scenario written happened to be the one everyone already agreed on. The feature file gives the comforting appearance of specified behavior while specifying only the part nobody was going to get wrong.

Same-shaped examples in both stories. In one, built to find disagreement, and they did. In the other, built to document a decision already made alone, and they found nothing — because they weren't looking.

Runnable takeaway: take one "obvious" rule from your backlog and force a boundary example with real dates and dollar amounts in front of the PM. If nobody flinches, you lost thirty minutes. If someone says "wait, that's not what I meant," you just found a production incident in a conference room.

The one test that separates practice from cargo cult

You do not need a maturity audit to know which one you have. There is a single diagnostic — the discovery-first question this whole topic turns on:

Is a business stakeholder actually reading and editing these examples?

Not "could they, in principle." Not "we invited them to the folder." Actually reading them, changing values, saying "no, the refund answer there is wrong." That behavior is the entire justification for Gherkin's existence. Gherkin costs you a second language and a layer of glue code for exactly one benefit: a non-programmer can read and correct the specification. If no non-programmer ever does, you are paying the full price of that structured English and receiving none of its value — you'd be strictly better off writing plain-code tests in the language your team already speaks.

This reframes what "BDD adoption" even means. It is not measured by the count of feature files or the presence of Cucumber in package.json; it is measured by whether the examples changed hands. Twenty scenarios a PM edits weekly is BDD. Four hundred scenarios no non-engineer has opened is expensive test formatting. (This is the same discovery move behind 'Testing Fundamentals: Why We Test' — the value is in the shared understanding, not the artifact.)

Runnable takeaway: count the number of edits to your .feature files in the last 90 days whose author is not on the engineering team. Zero is a diagnosis, not a rounding error — it means drop the Gherkin layer or start holding the conversation it was built to capture.

When BDD pays off, and when it's cargo cult

The practice is not universally right. Its cost — the conversations, the second language, the glue — is only repaid under specific conditions. Match your situation against the signals honestly.

Signal

BDD pays off

It's cargo cult

Who reads the examples

A PM, analyst, or domain expert reads and edits them

Only engineers ever open the .feature files

Nature of the rules

Contested, nuanced, or easy to misread ("within 14 days")

Mechanical and self-evident to everyone

Where examples are built

Together, before code, to surface disagreement

Alone, after code, to document a done decision

What conversation happens

Real debate that changes the requirement

Silent translation of one person's mental model

Regulatory / cross-team stakes

Specs must be legible to auditors or several teams

Internal tool, one team, no external reader

What a scenario catches

Requirement bugs before implementation

Nothing the author didn't already know

The pattern across the table is one axis: does someone outside engineering engage with the examples, and do the examples surface something? Left of that is the airplane; right of it is the airport.

Runnable takeaway: score your current use against these six rows. Three or more in the right-hand column means you are maintaining Gherkin as decoration — either recruit the stakeholder the format exists to serve, or delete the layer and test in plain code.

How to run the conversation (the actual method)

If the diagnosis says you have the airport but the conditions say the practice would pay off, here is the method — the conversation, not the tooling.

  1. Pick a story with a contested or ambiguous rule. Not "add a logout button." Something with a boundary, a policy, or a number people might read differently. Mechanical stories don't need this and shouldn't have it.

  2. Get the three amigos in a room for 20–30 minutes. Business (PM/analyst/domain owner), engineering (who builds it), testing (who breaks it). All three are required — the tester's job is to ask the awkward boundary question the other two want to skip.

  3. Invent examples out loud and agree on each answer. Start with the obvious happy path for momentum, then push to edges and boundaries. Use real, specific values — dates, dollar amounts, states — never "some customer" and "some window." Specificity is what forces the disagreement.

  4. When you disagree, you have found the point of the meeting. Resolve it, and update the requirement, not just the example. The example is the detector; the fixed requirement is the payload.

  5. Only now, capture the agreed examples in a format that runs — Gherkin if a stakeholder will keep reading them, plain-code tests if the engaged reader turns out to be another engineer. The format is the last decision, not the first.

Notice the tool choice is step five, not step one. The team from the opening did step five first and nothing else, which is exactly how you build an airport.

Runnable takeaway: schedule one 30-minute three-amigos session this sprint on your most argued-about rule, aiming to leave with one example that changed someone's mind. If none did, the rule really was obvious — and you've earned the right to skip the ceremony for it.

Tradeoffs and where this doesn't apply

BDD is a coordination tool, and coordination has overhead worth naming. The Given / When / Then structure is real friction — a second language over your test code, plus step-definition glue that must be maintained as behavior changes. That overhead is a bargain when it buys a non-programmer's comprehension and catches requirement bugs, and pure cost when it doesn't.

Where it genuinely doesn't apply:

  • Internal tools with no external stakeholder. If the only people who understand and care about the behavior are the engineers building it, the readability benefit has no audience. Test in plain code.

  • Mechanical, self-evident logic. A rule nobody could misread gains nothing from being examined in a room. The disagreement-detector detects nothing.

  • Solo or tiny teams with tight shared context. When one person holds the business and the code, the conversation is already happening in one head; formalizing it can be waste.

Conversely, BDD earns its cost hardest where rules are contested, stakeholders genuinely participate, specs must satisfy auditors or regulators, or several teams coordinate on the same behavior — the same conditions that make ambiguity expensive, which is why the discipline that surfaces ambiguity early pays there. This is the boundary drawn in 'Definition of Done vs. Acceptance Criteria': acceptance criteria describe what to agree on; BDD is how you drive that agreement into the open before it costs you.

Runnable takeaway: before adopting BDD for a team or a feature, name the specific non-engineer who will read the examples. If you cannot name a person, you are choosing the format for its looks — pick plain tests and revisit when a real reader appears.

What to do next

You do not need a framework decision this week — you need one honest measurement and one conversation. Run the audit: count non-engineering edits to your .feature files over the last 90 days; that number tells you whether you have the practice or the airport. Then take your single most-argued-about backlog rule into a 30-minute three-amigos session and try to build one boundary example that changes someone's mind. The first action reveals what you actually have; the second shows what the practice feels like when it works.

If the examples change hands and change minds, keep the format that keeps your stakeholder reading. If they never leave engineering's hands, drop the second language and test in the one your team already speaks. Either way you'll have stopped mistaking the runway for the flight. The same discipline extends downstream into 'User Acceptance Testing (UAT): How to Run It Well', where the examples you agreed on become the script the business signs off against.

Final takeaway

BDD is not a syntax and not a tool. It is the practice of building concrete examples with the business before the code, to catch the misunderstandings that abstract requirements hide. Gherkin and Cucumber only save the results of that practice — without it they are worse than worthless. The single question that tells you which you have is whether someone outside engineering actually reads and edits the examples. If they do, you built the airplane. If they don't, you built a very tidy airport, and no amount of green feature files will make the cargo land.

Sources

  • The Given / When / Then structure and the Gherkin language conventions referenced here follow the Cucumber project's published documentation for Gherkin and Cucumber.

  • The refund-eligibility three-amigos session is a developed composite scenario — an illustrative example constructed to demonstrate how building examples together surfaces a requirement ambiguity. It is not a report of a specific named organization.

Keep learning. This article is part of the Software Testing Foundations path in the ShiftQuality Learning Center.

bottom of page