TDD, BDD, ADD, SDD, EDD: Which Do You Actually Need? A Practical Guide for the AI Era
short glimpse TL;DR: Five acronyms, one question: how do you know the software does what you wanted? TDD verifies your code. BDD verifies your behaviour. ADD / ATDD verifies your agreement. SDD verifies the work of your AI coding agent. EDD verifies the AI output you ship. You don't pick one. You s

TL;DR: Five acronyms, one question: how do you know the software does what you wanted?
TDD verifies your code. BDD verifies your behaviour. ADD / ATDD verifies your agreement. SDD verifies the work of your AI coding agent. EDD verifies the AI output you ship. You don't pick one. You stack them — and this article tells you where each one goes.
Five terms, five philosophies. And if you've been drowning in newsletters lately,
you've probably felt the same confusion: is this a testing strategy? A process? A mindset? Are they fighting each other?
My take on this is: none of these are really "testing strategies." They're all answers to the same question, asked at different moments in software history:
How do you know the software does what you want?
we are in the phase - AI writing the code. Writing code got cheap, fast, and almost free. Verifying code got more expensive, not less. These five practices, in different ways, are all verification tools.
1990s 2003–06 2000s 2024–26 2025 │ │ │ │ │ TDD ───────────► BDD ADD EDD SDD Kent Beck Dan North agile testing Hamel Husain GitHub Spec Kit (Extreme (grew out of (usually written & Shreya Shankar, + OpenSpec Programming) TDD) as ATDD) Airbnb practice │ │ │ │ │ └────────────────┴──────────────────┘ │ │ "know what to build" │ │ (tests, behaviours, the agreement) │ │ └────────┬────────┘ │ "verify what an AI just built (and what it shipped)" Enter fullscreen mode Exit fullscreen mode The split is not a coincidence. The first three exist to prevent building the wrong thing. The last two exist because a new builder entered the room — one that's fast, tireless, and has zero stake in your product.
Test-Driven Development was formalized by Kent Beck in the late 1990s, as part of Extreme Programming. The standard summary of the loop:
┌───────────┐ ┌───►│ RED │ write the next failing test │ └─────┬─────┘ │ ▼ │ ┌───────────┐ │ │ GREEN │ write just enough code to pass │ └─────┬─────┘ │ ▼ │ ┌───────────┐ │ │ REFACTOR │ clean it up, tests stay green │ └─────┬─────┘ │ │ └──────────┘ (repeat, forever) Enter fullscreen mode Exit fullscreen mode Why it works Two benefits, per Martin Fowler.
First, your code is self-testing — you can only write production code in response to making a test pass.
Second, and the underrated one: thinking about the test forces you to think about the interface first. You design how the thing is used before deciding how it's built.
The most common way people screw up TDD is skipping step three. No refactoring means a messy aggregation of code fragments — the tests stay green, the code doesn't.
Behaviour-Driven Development was coined by Dan North. North was teaching TDD and kept hitting the same wall: beginners asking where do I start, what should I test, what do I call this, why did it fail?
His answer was a reframe. Stop saying test. Start saying behaviour.
Feature: User login Scenario: Successful login Given a registered user exists When the user submits valid credentials Then they are redirected to the dashboard Enter fullscreen mode Exit fullscreen mode The mechanics worth knowing:
BDD is for deterministic systems. The output of a login form is either correct or it isn't. assert redirect_url == "/dashboard" is a valid test. Tools: Cucumber, SpecFlow, Behave, pytest-bdd, Jest + Testing Library.
Acceptance-Driven Development / ATDD (Acceptance Test-Driven Development).
The idea is simple: before anyone writes a line of code, the team — developers, testers, and the business person — writes the acceptance criteria for the story. In natural language. End to end.
───────── ╱ ADD ╲ acceptance: "what does done mean?" ╱ the ╲ team signs off BEFORE any code ╱ contract ╲ ╱───────────────╲ ╱ BDD ╲ behaviour: "what should it do?" ╱ Given/When/ ╲ scenarios in plain English ╱─────────────────────╲ ╱ TDD ╲ unit: "does this function work?" ╱ red-green- ╲ smallest, fastest, cheapest ╱───────────────────────────╲ Enter fullscreen mode Exit fullscreen mode Read that pyramid from the bottom up: TDD checks the parts, BDD checks the behaviour, ADD checks the agreement. Each level is slower, coarser, and closer to the business.
Story: As a user, I want to reset my password, so that a compromised account is less dangerous.
That's the whole practice. The criteria are written before implementation, reviewed by the people whose jobs depend on the feature, and become the tests the finished feature must pass. Each of the three roles gets something:
Spec-Driven Development is the newest heavyweight.
The fundamental problem SDD solves: if you let an AI agent write both the component and the tests for it, the agent will unconsciously write **tests that pass — not tests that verify.
** SDD ** breaks that cycle by locking the spec before any code exists. The spec is written by a human. The code is written by the agent.
The spec is the non-negotiable contract between them.
OpenSpec's mental model is the contract and the contractor: a contractor who shows up with no blueprint builds whatever they think you meant. OpenSpec is the contract. The agent is the contractor.
/speckit-constitution once per project: principles (quality, tests, style) │ ▼ /speckit-specify ──► /speckit-plan ──► /speckit-tasks what & why stack & the spec, broken constraints into an ordered task list │ ▼ ┌────────────────────────────────────┐ │ /speckit-implement │ │ │ │ │ ▼ │ │ /speckit-converge │ │ does the code match the spec? │ │ │ no │ yes │ │ └───────────► "Converged" │ │ (fix & re-implement) │ └────────────────────────────────────┘ Enter fullscreen mode Exit fullscreen mode Two details deserve emphasis. The constitution is the spec for specs — your project's non-negotiables, written once.
converge is a verb the pre-AI world didn't need: TDD asks "do the tests pass?"; SDD asks something stricter — does what was built match what was specified, end to end? The spec is the source of truth; the code is the claimant.
/opsx:explore optional: a no-stakes thinking partner — reads the codebase, shapes the plan │ ▼ /opsx:propose <feature> agent writes the spec. No code yet. │ ▼ /opsx:apply <feature> agent writes code strictly following the spec │ ▼ /opsx:archive <feature> the change is folded into the main spec, permanently Enter fullscreen mode Exit fullscreen mode Lighter than Spec Kit: each change is a folder of plain Markdown — proposal.md, specs/ (requirements and scenarios), design.md, tasks.md — reviewed by a human before any code is written.
Command spellings vary by tool ( /opsx-propose, $openspec-propose ); openspec init prints the right form for yours.
Agents are bad at holding thirty requirements in their head across a long session. They're good at following a written document step by step. A spec is a leash with a purpose: it keeps intent outside the model — in a file that survives session restarts, model swaps, context windows, and team changes.
Tell an agent a feature in chat, and the intent dies with the conversation. Write it in a spec, and it outlives everything except your product.
Eval-Driven Development that exists because of a specific technical fact: LLM output is probabilistic.
You write assert response == "Your order has shipped." and you've written a test that will fail on the first Tuesday of the month, for reasons you cannot predict.
Unit tests assume determinism. Probabilistic output breaks that assumption, so the whole verification story has to be rebuilt. That rebuild is EDD.
The term was formalized in methodology by Hamel Husain and Shreya Shankar, and got an academic process model in the arXiv paper "Evaluation-Driven Development and Operations of LLM Agents".
1. RUN the system, collect traces │ ▼ 2. ERROR ANALYSIS read 30–100 traces, note every failure, group notes into a failure taxonomy │ ▼ 3.
BUILD an eval for each failure that matters cheap first: code assertions, regex, JSON checks, "did it call the right tool?" expensive last: LLM-as-judge for judgment calls, validated against 100–200 human labels before trusting it │ ▼ 4. FIX → re-run → improved, without regressing? │ └────────► new traces, new failures, repeat Enter fullscreen mode Exit fullscreen mode Hamel and Shankar put it in their own FAQ: on serious projects, 60–80% of development time goes to error analysis and evaluation — that is, looking at data, not building dashboards. That ratio is the practice.
If your eval workflow is 90% dashboard and 10% looking, you have the wrong workflow.
The naive version of EDD says: "build a golden dataset and a scoring rubric first, then develop."
Here's the correction, from the field's most-cited authority. Hamel Husain's own answer to "Should I practice eval-driven development?" is generally no. His reasoning: unlike traditional software, where failure modes are predictable, LLMs have an infinite surface area for potential failure. You can't anticipate what will break. Writing evals before the system exists means writing evals for failures you imagined — wasted effort, and it blocks you on questions you can't answer until the system exists.
Do error analysis first. Write evals for failures you found, not failures you guessed.
The golden dataset is real and important — but it grows out of error analysis, as a version-controlled set of representative examples and expected quality. It's not something you author on a whiteboard in week one.
One exception: if you have a constraint where you already know exactly what success looks like — "the model must never mention competitor X" — writing that evaluator early is fine. It's a known boundary, not a prediction.
Tools: LangSmith, Braintrust, RAGAS, Arize Phoenix, Weights & Biases Weave.
This is the most common confusion in the field, so here's the clean split:
SDD is for people who are coding and creating applications using coding agents. EDD is for people who are building AI-powered features as their product.
SDD's failing signal EDD's failing signal ┌───────────────────────────┐ ┌───────────────────────────┐ │ expected button to be │ │ faithfulness score 0.62 │ │ visible — FAIL │ │ < threshold 0.80 │ └───────────────────────────┘ └───────────────────────────┘ deterministic · boolean probabilistic · scored "the spec says visible" "the judge says not faithful" Enter fullscreen mode Exit fullscreen mode One sentence: SDD verifies that the agent built what you specified. EDD verifies that what you built is actually good.
Stop treating these as competitors. They stack. Here's the map:
What are you building / verifying? │ ├─ deterministic code, small change ────────────► TDD │ ├─ business rule, multiple roles in the room ──► BDD │ ├─ "what counts as done?" is ambiguous, ───────► ADD │ stakeholders must sign off │ ├─ new feature / app built by an AI agent ─────► SDD │ ├─ AI / LLM output shipped as a product ───────► EDD │ └─ a real product (most projects) ─────────────► all of them, layered ↓ Enter fullscreen mode Exit fullscreen mode The reference table Situation Start with Why Bug fix in deterministic logic TDD A failing test reproduces the bug and proves the fix Auth / billing / payment rules BDD Given/When/Then in the domain's own language PM + dev + tester must agree on "done" ADD The contract is signed before the machine starts Greenfield app via Copilot / Cursor / Claude Code SDD The spec anchors the agent across sessions Chatbot, RAG pipeline, or AI agent EDD Probabilistic output — asserts don't apply Swapping models (GPT → Claude → whatever) EDD Same prompt, different model = different quality A screen / API / service you write yourself TDD (+ BDD if it has domain rules) Deterministic — unit tests are the right tool On-device ML inside a mobile app EDD Precision / recall / latency, not assert Everything above, in one product Layer them See the worked example below One Feature, Five Practices ┌─────────────────────────────────────────────┐ │ SDD — the spec │ intent: what we decided to build ├─────────────────────────────────────────────┤ │ ADD + BDD — the acceptance criteria │ agreement: what "done" means ├─────────────────────────────────────────────┤ │ TDD — the unit tests │ implementation: the code's truth ├─────────────────────────────────────────────┤ │ EDD — the evals (the AI parts only) │ quality: the output's truth └─────────────────────────────────────────────┘ Enter fullscreen mode Exit fullscreen mode Case 1: You're building a photo organizer with AI tagging: Photo Organizer with AI tagging │ ├─ SDD spec: "albums grouped by date, tile preview, │ AI tags on import, tags editable" ├─ ADD criteria (with the PM): "one album per month; │ a tag can be deleted; suggestions appear within │ 60s for a 100-photo import" ├─ BDD behaviour: timezone grouping, in Given/When/Then — │ exactly where "done" gets ambiguous ├─ TDD code: date logic, tag-edit API, preview pagination — │ red/green/refactor, agent driving the loop └─ EDD AI tagging: collect traces of real imports, read 30, build the taxonomy ("miscategorizes group photos", "hallucinates tags for non-photo files"), one eval per top failure, fix, re-run ### Case 2: Mobile and On-Device ML Mobile is where this gets interesting, because a single app might need *multiple* methodologies at once, applied to different parts of the same codebase. Enter fullscreen mode Exit fullscreen mode plaintext A mobile app (Android / iOS / React Native / Flutter) │ ├─ app code: screens, navigation, business logic │ │ │ ├─ you write it ──────────────► TDD / BDD │ │ (XCTest, Espresso, JUnit) │ │ │ └─ an AI agent writes it ─────► SDD │ (spec first — lifecycle, offline │ sync, and memory rules live IN the │ spec, before the agent touches a file) │ └─ on-device ML feature ──────────────► always EDD (golden set + precision / recall / latency / RAM)
**Why EDD is harder on mobile.** On-device ML adds a hardware dimension server-side EDD doesn't have. Your golden dataset must cover not just output quality but: - **RAM footprint** — peak and steady-state across low-end, mid-range, and flagship devices - **Thermal and battery impact** — CPU / GPU / NPU utilisation under sustained load - **Inference latency** — time-to-first-token on *real devices*, not simulators (which are too fast and too uniform) | Platform | ML frameworks | EDD tooling | |---|---|---| | **iOS** | Core ML, Create ML, Apple Foundation Models | Xcode Instruments (Neural Engine, Foundation Models), XCTest assertion harnesses | | **Android** | TensorFlow Lite, MediaPipe, ExecuTorch (NNAPI / GPU delegates) | Android Studio Profiler, custom JUnit eval harnesses against test tensors, Firebase Test Lab | | **React Native / Flutter** | Core ML / NNAPI via platform channels, ONNX Runtime, ExecuTorch | Firebase Test Lab, BrowserStack real devices, host-side harness driving the native runtime | The split never changes, whatever the platform: **the app code follows TDD/BDD or SDD depending on who writes it; the AI feature inside the app is always EDD.** Enter fullscreen mode Exit fullscreen mode One feature. Five practices.
None of them redundant. The spec carried the intent. The acceptance criteria carried the agreement.
The behaviours carried the domain rules. The tests carried the code. The evals carried the AI quality.
Remove any one and something leaks.
The tools are new, so the failures are new too.
1. Trusting the agent's green light. "The tests passed" — whose tests, run where, by whom? Run the suite yourself. Read the diff. Green means "the tests you have pass." It does not mean "correct." In the agent era, your review of the diff is the review of the agent, not of a colleague.
2. Writing evals for failures you imagined. Hamel's "generally no" applies. If your eval suite was written before the product existed, it measures your expectations, not the product. Error analysis first, always.
3. Chasing 100% eval pass. A perfect score is a signal that your evals are soft. The teams getting value live at 70–85% on hard checks and slowly ratchet the bar.
4. Spec rot. The spec says one thing, the code does another, nobody noticed for three sprints. That's what the converge step exists for. A spec that isn't re-verified is a rumor.
5. Platform before practice. Buying the fancy eval dashboard in week one instead of spending 30 minutes reading traces. The data is the practice. The platform is furniture.
6. TDD theater. Tests written after the code that merely mirror the implementation. That's not TDD, that's a screenshot of the code. The test's job is to state intent — the thing that should be true independent of how the code got there.
Five steps if you want to do something this week:
Every one of these practices is a response to the same fear: I built it, and it's not what I wanted.
TDD answered that fear for functions. BDD answered it for behaviours and shared language. ADD answered it for teams and stakeholders. And when the builders became agents. SDD answered it for intent — what did we actually decide to build? EDD answered it for quality — is the AI output actually good, and how do we know?
You don't have to pick one. You don't have to master all five. Pick the one that matches the failure you're most afraid of this week, and run it badly at first. Then run it less badly. The practices compound: every spec you write, every behaviour you articulate, every eval you fix, makes the next build cheaper to verify.
Code is cheap now. The scarce resource was never typing — it was knowing. These five practices are all just different languages for the same sentence:
The sources behind this article, in the order they were used: