Engineering5 min read

Test-Driven Development

Practice Test-Driven Development - the red-green-refactor cycle, outside-in TDD, BDD with Cucumber/Gherkin, and making TDD sustainable in team workflows.

TDDBDDRed-Green-RefactorOutside-In TDDAcceptance Testing

skill: Test-Driven Development category: Engineering tags: [TDD, BDD, Red-Green-Refactor, Outside-In TDD, Acceptance Testing] readingTime: 5

TDD is a design tool, not just a testing tool. Writing the test first forces you to think about the interface before the implementation - the test is the first client of your code. If writing the test is painful, the design is wrong. TDD doesn't slow you down; it eliminates the debugging sessions and late-discovered design mistakes that slow you down.

Workflow

  1. Master the red-green-refactor cycleThe three-step cycle creates a tight feedback loop. Red catches bugs early; green gets to working code fast; refactor improves design with confidence (tests guard against regression).
  2. Practice outside-in TDDOutside-in TDD (London School) starts from the outermost layer (acceptance test) and works inward through the layers. This ensures you only build what's needed and naturally discovers collaborator interfaces.
  3. Write behavior-driven development (BDD) specsBDD extends TDD with business-readable specifications (Gherkin). Non-technical stakeholders can read, validate, and contribute to specifications that also serve as automated tests.
  4. Use TDD for design feedbackPainful test setup is a design signal. If you need 10 lines of setup to test 3 lines of logic, the SUT (Subject Under Test) has too many dependencies or is doing too many things.
  5. Make TDD work in team environmentsTDD works best when the whole team practices it. Partial adoption creates friction - code without tests blocks TDD practitioners from refactoring safely.

Output Format

TDD: strict red-green-refactor (never write production without failing test), outside-in double-loop acceptance→unit, BDD Gherkin Feature/Scenario/Given-When-Then, design signals from test pain, and team TDD adoption via pairing + kata.

RED: write one failing test for one behavior
GREEN: minimum code to pass (even return hardcoded)
REFACTOR: improve design while tests stay green
Outside-in: acceptance test → unit test → implement → acceptance passes
BDD: Feature → Scenario → Given/When/Then → step definitions

Examples

Input

Output

Edge Cases & Gotchas

  • TDD for database migrations - Database migrations are infrastructure changes, not business logic - test them with integration tests that check schema shape, not unit tests. TDD approach: write a test asserting the migration can run up and down.
  • TDD for UI components - For React components, test rendered output and user interactions: render(component) → userEvent.click(button) → expect(screen.getByText("result")). Don't test DOM structure - test what the user sees and can do.
  • Emerging design vs. up-front design - TDD produces emergent design - let the tests drive the structure. This is uncomfortable if you're used to design-first. Trust the process for the first few iterations; refactoring in step 3 is where the design emerges.
  • TDD and type systems - In TypeScript, compiler errors count as "red" - a test that doesn't compile is still driving design. Write the test file, let TypeScript errors guide you to define the right types and interfaces.

When to Break These Rules

  • Don't apply TDD to data-heavy exploration (EDA, ML experiments) where the "test" is your eyes on a plot. TDD shines for discrete behavior with clear inputs and outputs.
  • Don't apply TDD rigidly to infrastructure-as-code (Terraform, Helm charts) - use integration tests (terratest, helm test) after writing, not before. The feedback cycle is too slow for TDD.

Reference Files

  • https://martinfowler.com/bliki/TestDrivenDevelopment.html
  • https://cucumber.io/docs/bdd/

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.