design·lab

Design approach

Test-Driven Development

Write a small failing test before the code that makes it pass — the test drives the design, not the other way around.

✗ The problem

Code first, test later (or never)

Write the whole implementation at once, "looks right", ship it. Tests — if any — get retrofitted afterward to match whatever the code already does, bugs included.

function fizzbuzz(n) {
  // written all at once, "looks right"…
  if (n % 3 === 0 && n % 5 === 0) return "FizzBuzz";
  if (n % 3 === 0) return "Fizz";
  if (n % 5 === 0) return "Buzz";
  return n;   // ← bug: number, not string. Nothing caught it.
}
// tests written LATER, "once it works on my machine"
No test pins the contract until it's convenient. Edge cases (negatives, zero) go untested, the bug hides for weeks, and refactoring feels dangerous with nothing to catch a regression.
✓ The approach

Red → Green → Refactor, on repeat

Tiny loop, repeated for every new bit of behavior. Tests come first — they drive what the code needs to do next.

// 1. RED — write one small failing test
test('sum(2,3) === 5', …);

// 2. GREEN — minimum code to pass
function sum(a, b) { return a + b; }

// 3. REFACTOR — clean up, tests stay green
// rename, dedupe, simplify… then repeat →
🔴 RED
failing test
🟢 GREEN
minimum code
🔵 REFACTOR
clean, safely
✓ See it live

Build fizzbuzz() one red-green-refactor loop at a time

Click through the loop. Watch a new failing test appear, get made to pass with the smallest change, then get cleaned up — while every earlier test stays green.

// …
RED Feature 1/4
✓ Takeaway

Tests are design pressure, not an afterthought

  • Design pressure: a test that's awkward to write is telling you the design is awkward — fix that first.
  • Living spec + regression net: the suite documents intended behavior and catches breaks instantly.
  • Small steps, real courage: tiny red-green loops make refactoring safe — you always have a green baseline to fall back to.
  • Caution: TDD is not a substitute for thinking about design up front, and tests should verify behavior, not internal implementation details.
🎯 Relates: TDD makes Dependency Inversion pay off (inject mocks), rewards small single-responsibility units, and keeps code simple.