Software Engineering

TDD and BDD

Kent Beck, the inventor of TDD, once described being asked to implement a multi-currency financial system. Instead of designing an architecture, he wrote one test: expect(Money.dollar(5).plus(Money.franc(10))).equals(Money.dollar(10)). The test did not compile. But it captured the entire essence of the system. Two hours of red-green cycles later, the architecture had formed on its own - and it turned out simpler than anything that could have emerged from classical upfront design.

  • **Google:** internal research found that projects with high testing culture (including TDD practices) have significantly lower production incident rates
  • **Pivotal/VMware:** all product code is written in pairs and through TDD; this is a core engineering practice of the company, not optional
  • **NHS Digital (UK):** migrating critical medical systems to BDD with Gherkin allowed clinical specialists to verify specifications without a technical intermediary

Red-Green-Refactor Cycle

TDD is not about testing. It is about design. Writing a test before code forces thinking about the API, interfaces, and contracts before thinking about implementation. A developer who writes the test first automatically becomes the first consumer of their own API - and immediately sees whether it is pleasant to use. The red test is a specification. The green test is the minimal implementation. Refactoring is the cleanup.

Three Laws of TDD (Robert C. Martin): 1) Production code may not be written unless a failing test exists. 2) No more of a failing test may be written than is sufficient to fail. 3) No more production code may be written than is sufficient to pass the failing test. The cycle: minutes, not hours. No need to design a perfect architecture upfront - it emerges through refactoring.

In TDD, why is it important to confirm the test FAILS (red) before writing the implementation?

Given-When-Then and BDD

BDD is TDD with a different vocabulary. Where TDD says red-green-refactor, BDD says given-when-then. The difference is not technical - it is communicational. Given-When-Then describes behavior in language that not only the developer understands, but also the product manager, QA, and business stakeholders. The specification becomes executable documentation.

Given-When-Then (GWT): Given - the initial context (preconditions), When - the action or event, Then - the expected outcome. Gherkin is the DSL for writing GWT scenarios in a human-readable format. Cucumber (Java/Ruby), SpecFlow (.NET), Behave (Python) - frameworks that execute Gherkin files. Jest/Vitest use describe/it for an informal GWT structure.

What is the primary goal of BDD compared to standard unit testing?

Test-First Design

Test-first reverses the force of gravity: instead of 'write code, add test', it becomes 'write test, code grows from it'. The main consequence is testable-by-design architecture. Code written test-first typically has fewer dependencies, clearer boundaries of responsibility, and more explicit injections - because otherwise it would be impossible to test it in isolation.

Signs of hard-to-test code: hidden dependencies (new Database() inside a function instead of injection), global state, mixing business logic with I/O. Test-first discovers these problems immediately - the test will demand dependency injection. This is one of the strongest arguments for TDD: bad architecture causes pain when writing the very first test.

A developer writes a test for a new class and finds that 8 parameters are needed to construct the object. What does this signal in TDD?

BDD Tools: Vitest and Cucumber

Vitest and Jest provide BDD-like syntax through describe/it out of the box - sufficient for most projects. Cucumber with Gherkin files is warranted when non-technical stakeholders (product managers, analysts) need to write or read specifications. This is a two-way contract: Cucumber demands discipline to keep step definitions current.

Vitest is the modern alternative to Jest for Vite projects: compatible API, native ESM, built-in TypeScript support, workspace support for monorepos. Vitest UI - a browser interface to view and filter tests. @cucumber/cucumber for Node.js: Gherkin scenarios -> step definitions -> assertions. Living documentation via Cucumber HTML Reports.

TDD slows down development because you write twice as much code

TDD slows the start but speeds up iterations: less debugging, safer refactoring, fewer regressions

Research (Microsoft Research, Google) shows TDD increases writing time by 15-35% but reduces defects by 40-90%. Savings from less debugging and fewer hotfixes more than compensate for the initial investment over just a few iterations.

When should you use Cucumber with Gherkin instead of just Jest/Vitest with describe/it?

Key Ideas

  • **Red-Green-Refactor** - not about tests, about design; test before code makes the API comfortable for its consumer
  • **Given-When-Then** - a universal language between business and engineering; describes behavior, not implementation
  • **Test-first** reveals architectural problems immediately: hard-to-test code is a sign of poor architecture
  • **Cucumber vs Jest/Vitest** - choose based on real need; Gherkin is only warranted if non-technical stakeholders read the specifications

Related Topics

TDD and BDD define the rhythm of the entire engineering practice:

  • Integration and E2E Testing — TDD applies at the unit level; BDD scenarios are often implemented as integration or E2E tests
  • Refactoring and Clean Code — Test coverage is the primary enabler of safe refactoring

Вопросы для размышления

  • If TDD improves code design, why do many experienced developers avoid it in practice? What is the real barrier?
  • How do you apply TDD to I/O-heavy systems (HTTP calls, file system, message queues)? Where is the line between mocking and real testing?
  • BDD specifications in Gherkin can go stale without discipline to keep them updated. How do you organize the process so that living documentation stays alive?

Связанные уроки

  • stat-05-hypothesis
TDD and BDD

0

1

Sign In