10 min read • Guide 832 of 877
Test-Driven Development Workflow
Tests drive design. GitScrum helps teams track TDD adoption and measure the impact of test-first practices on quality and velocity.
TDD Fundamentals
The Red-Green-Refactor Cycle
TDD CYCLE:
┌─────────────────────────────────────────────────────────────┐
│ │
│ THE CYCLE: │
│ ────────── │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ │ │
│ │ ┌────────┐ │ │
│ │ │ RED │ Write failing test │ │
│ │ └───┬────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────┐ │ │
│ │ │ GREEN │ Make it pass │ │
│ │ └───┬────┘ (minimal code) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────┐ │ │
│ └────┤REFACTOR│ Improve design │ │
│ └────────┘ (tests still pass) │ │
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ STEP 1 - RED: │
│ Write a test for behavior that doesn't exist yet │
│ Run it → It fails (proves test works) │
│ │
│ STEP 2 - GREEN: │
│ Write just enough code to pass the test │
│ Don't over-engineer, don't optimize │
│ Just make it work │
│ │
│ STEP 3 - REFACTOR: │
│ Now improve the code │
│ Remove duplication, clarify names, improve design │
│ Tests ensure you didn't break anything │
│ │
│ REPEAT for each small piece of behavior │
└─────────────────────────────────────────────────────────────┘
Example Workflow
TDD in Practice
TDD EXAMPLE:
┌─────────────────────────────────────────────────────────────┐
│ │
│ FEATURE: Calculate order total with discount │
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ STEP 1 - RED (Write failing test): │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ test('calculates total with 10% discount', () => { ││
│ │ const order = new Order([ ││
│ │ { price: 100 }, ││
│ │ { price: 50 } ││
│ │ ]); ││
│ │ order.applyDiscount(0.1); ││
│ │ expect(order.total()).toBe(135); ││
│ │ }); ││
│ │ ││
│ │ RUN → ❌ FAIL (Order doesn't exist) ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ STEP 2 - GREEN (Make it pass): │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ class Order { ││
│ │ constructor(items) { ││
│ │ this.items = items; ││
│ │ this.discount = 0; ││
│ │ } ││
│ │ ││
│ │ applyDiscount(rate) { ││
│ │ this.discount = rate; ││
│ │ } ││
│ │ ││
│ │ total() { ││
│ │ const subtotal = this.items.reduce( ││
│ │ (sum, item) => sum + item.price, 0 ││
│ │ ); ││
│ │ return subtotal * (1 - this.discount); ││
│ │ } ││
│ │ } ││
│ │ ││
│ │ RUN → ✅ PASS ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ STEP 3 - REFACTOR (Improve): │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ • Extract subtotal calculation ││
│ │ • Add validation ││
│ │ • Improve naming ││
│ │ • Run tests → Still passing ✅ ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ NEXT: Write another test for next behavior │
│ (e.g., maximum discount, negative prices, etc.) │
└─────────────────────────────────────────────────────────────┘
Benefits
Why TDD Works
TDD BENEFITS:
┌─────────────────────────────────────────────────────────────┐
│ │
│ BETTER DESIGN: │
│ ────────────── │
│ Writing tests first forces you to think about: │
│ • How code will be used (API design) │
│ • What the code should do (requirements) │
│ • How to make it testable (loose coupling) │
│ │
│ Result: Cleaner, more modular code │
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ CONFIDENCE: │
│ ─────────── │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ WITHOUT TESTS: ││
│ │ "Does this change break something?" ││
│ │ → Manual testing, uncertainty, fear ││
│ │ ││
│ │ WITH TDD: ││
│ │ "Does this change break something?" ││
│ │ → Run tests → Know in seconds ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ DOCUMENTATION: │
│ ────────────── │
│ Tests document what code should do │
│ Executable documentation that can't go stale │
│ New developers read tests to understand behavior │
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ LESS DEBUGGING: │
│ ─────────────── │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ WRITE CODE FIRST: ││
│ │ Write → Test manually → Find bugs → Debug → Fix ││
│ │ Time: 2 hours coding + 3 hours debugging = 5 hours ││
│ │ ││
│ │ TDD: ││
│ │ Test → Code → Pass → Repeat ││
│ │ Time: 3 hours (includes tests) + 30 min debug ││
│ │ ││
│ │ Bugs caught immediately, not hours later ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
Test Types
What to Test
TESTING PYRAMID WITH TDD:
┌─────────────────────────────────────────────────────────────┐
│ │
│ /\ │
│ / \ E2E Tests │
│ / UI \ (Few, slow, broad) │
│ /──────\ │
│ / \ Integration Tests │
│ / Service \ (Some, medium speed) │
│ /────────────\ │
│ / \ Unit Tests │
│ / Unit \ (Many, fast, focused) │
│ /──────────────────\ │
│ │
│ TDD FOCUS: │
│ ────────── │
│ Primarily unit tests │
│ Fast feedback loop │
│ Test one thing at a time │
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ WHAT TO TEST IN TDD: │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ ││
│ │ ✅ BUSINESS LOGIC: ││
│ │ • Calculations ││
│ │ • Validation rules ││
│ │ • State transitions ││
│ │ • Domain behavior ││
│ │ ││
│ │ ⚠️ INTEGRATION POINTS: ││
│ │ • API contracts ││
│ │ • Database queries (integration tests) ││
│ │ • External service calls (mocked in unit tests) ││
│ │ ││
│ │ ❌ DON'T TDD: ││
│ │ • Framework code (already tested) ││
│ │ • Simple getters/setters ││
│ │ • UI layout (use other testing) ││
│ │ • Throwaway prototypes ││
│ │ ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
Team Adoption
Implementing TDD
ADOPTING TDD:
┌─────────────────────────────────────────────────────────────┐
│ │
│ PHASE 1: LEARN (Week 1-2) │
│ ───────────────────────── │
│ • TDD kata/workshop │
│ • Practice on toy problems │
│ • Pair with experienced TDD practitioner │
│ • Read/watch TDD resources │
│ │
│ PHASE 2: NEW CODE (Week 3-4) │
│ ──────────────────────────── │
│ • Apply TDD to new features │
│ • Start with simple stories │
│ • Pair programming recommended │
│ • Expect slower at first │
│ │
│ PHASE 3: EXPAND (Month 2+) │
│ ────────────────────────── │
│ • More complex features │
│ • Add tests when modifying existing code │
│ • Develop team testing patterns │
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ COMMON CHALLENGES: │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ ││
│ │ "It's slower!" ││
│ │ → Initially yes. Track time saved in debugging. ││
│ │ Long-term velocity increases. ││
│ │ ││
│ │ "I don't know what test to write" ││
│ │ → Start with the simplest behavior. ││
│ │ What's the most basic thing it should do? ││
│ │ ││
│ │ "How do I test this complex thing?" ││
│ │ → If it's hard to test, design might need work. ││
│ │ TDD pushes you toward better design. ││
│ │ ││
│ │ "We have too much legacy code" ││
│ │ → TDD new code. Add tests when touching old code. ││
│ │ Gradual improvement over time. ││
│ │ ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
Best Practices
TDD Guidelines
TDD BEST PRACTICES:
┌─────────────────────────────────────────────────────────────┐
│ │
│ TEST NAMING: │
│ ──────────── │
│ ❌ test1(), testCalculate() │
│ │
│ ✅ 'should calculate total with tax' │
│ ✅ 'returns error when email invalid' │
│ ✅ 'applies discount only to eligible items' │
│ │
│ Name describes expected behavior │
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ ONE ASSERTION PER TEST: │
│ ──────────────────────── │
│ ❌ │
│ test('order works', () => { │
│ expect(order.total()).toBe(100); │
│ expect(order.itemCount()).toBe(5); │
│ expect(order.isValid()).toBe(true); │
│ }); │
│ │
│ ✅ │
│ test('calculates correct total', () => {...}); │
│ test('counts items correctly', () => {...}); │
│ test('validates order', () => {...}); │
│ │
│ Easier to identify failures │
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ SMALL STEPS: │
│ ──────────── │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ DON'T: Write complex test requiring 100 lines of code ││
│ │ DO: Write test requiring 5-10 lines of code ││
│ │ ││
│ │ Smaller steps = faster feedback = fewer bugs ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ DON'T SKIP REFACTOR: │
│ ──────────────────── │
│ Green is not done! │
│ Refactor before moving to next test │
│ Technical debt accumulates if you skip this │
└─────────────────────────────────────────────────────────────┘
Measuring TDD
Tracking Adoption
TDD METRICS:
┌─────────────────────────────────────────────────────────────┐
│ │
│ ADOPTION METRICS: │
│ ───────────────── │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ TDD ADOPTION - Q1 2025 ││
│ │ ││
│ │ Stories using TDD: ││
│ │ Jan: 20% ████ ││
│ │ Feb: 45% █████████ ││
│ │ Mar: 68% █████████████ ││
│ │ ││
│ │ Test coverage trend: ││
│ │ Jan: 45% ││
│ │ Feb: 58% ││
│ │ Mar: 72% ││
│ │ ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ ─────────────────────────────────────────────────────────── │
│ │
│ QUALITY IMPACT: │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ BUGS IN PRODUCTION: ││
│ │ ││
│ │ Before TDD (2024): 8 bugs/sprint average ││
│ │ After TDD (2025): 3 bugs/sprint average (-63%) ││
│ │ ││
│ │ REWORK TIME: ││
│ │ ││
│ │ Before: 25% of sprint on bug fixes ││
│ │ After: 10% of sprint on bug fixes (-60%) ││
│ │ ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ VELOCITY: │
│ ───────── │
│ Short-term: May dip 10-20% during learning │
│ Long-term: Often increases as debugging decreases │
│ │
│ Track both to show the investment pays off │
└─────────────────────────────────────────────────────────────┘