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?