Test-Driven Development workflow with RED-GREEN-REFACTOR, lore from Kent Beck, Michael Feathers, and Ousterhout's counterpoint
1. RED - Write failing test first (define expected behavior)
2. GREEN - Minimal implementation to pass (don't over-engineer)
3. REFACTOR - Clean up, remove duplication, run tests again
"The act of writing a unit test is more an act of design than verification."
"The most powerful feature-addition technique I know of is test-driven development."
"Kent Beck baked this habit of writing the test first into a technique called Test-Driven Development."
"The problem with test-driven development is that it focuses attention on getting specific features working, rather than finding the best design."
When TDD can hurt:
The balance:
✅ Use TDD for:
❌ Skip TDD for:
# 1. Write test, watch it fail
bun test src/thing.test.ts # RED - test fails
# 2. Implement minimal code to pass
bun test src/thing.test.ts # GREEN - test passes
# 3. Refactor, tests still pass
bun test src/thing.test.ts # GREEN - still passing
# 4. Repeat for next behavior
Write the assertion first, then work backwards:
// Start here
expect(result).toBe(42);
// Then figure out what 'result' is
const result = calculate(input);
// Then figure out what 'input' is
const input = { value: 21 };
Use multiple examples to drive generalization:
it("doubles 2", () => expect(double(2)).toBe(4));
it("doubles 3", () => expect(double(3)).toBe(6));
// Now you MUST implement the general solution
When the solution is obvious, just write it:
function add(a: number, b: number): number {
return a + b; // Don't fake this
}
When unsure, start with hardcoded values:
// First pass
function fibonacci(n: number): number {
return 1; // Passes for n=1
}
// Add test for n=2, then generalize
/\
/ \ E2E (few)
/----\
/ \ Integration (some)
/--------\
/ \ Unit (many)
--------------
When working on a bead:
beads_start(id="bd-123")beads_close(id="bd-123", reason="Done: tests passing")