Files
claudekit/skills/test-driven-development/references/tdd-decision-tree.md
T
2026-04-19 14:10:38 +07:00

5.3 KiB

TDD Decision Tree

Quick reference for deciding when and how to apply Test-Driven Development.


Decision: Should I Use TDD Here?

Is this code...
│
├─ Business logic or data transformation?
│  └─ YES: Always TDD. No exceptions.
│
├─ An API endpoint (REST, GraphQL, RPC)?
│  └─ YES: Always TDD. Write request/response tests first.
│
├─ A bug fix?
│  └─ YES: Always TDD. Write a failing test that reproduces the bug first.
│
├─ A utility function or helper?
│  └─ YES: Always TDD. These are the easiest to TDD — pure input/output.
│
├─ A database query or repository method?
│  └─ YES: Always TDD. Test the query behavior, not the SQL syntax.
│
├─ A state machine or workflow?
│  └─ YES: Always TDD. Test each transition.
│
├─ UI layout or styling (CSS, Tailwind, visual positioning)?
│  └─ TDD optional. Visual output is hard to assert meaningfully.
│     Use snapshot tests or visual regression tools instead.
│
├─ Configuration or environment setup?
│  └─ TDD optional. Test that config loads correctly, but don't
│     TDD every config value. Integration tests are more useful.
│
├─ A database migration?
│  └─ TDD optional. Test that migration runs forward and backward.
│     Don't TDD the migration SQL itself.
│
├─ A prototype or spike?
│  └─ TDD optional. Spikes are throwaway. But if the spike becomes
│     real code, stop and add tests before continuing.
│
├─ Third-party integration glue code?
│  └─ TDD the contract, not the integration. Write tests against
│     the interface you expect, mock the external service.
│
└─ Generated code (scaffolding, boilerplate)?
   └─ TDD optional. Test the generator if you wrote it.
      Don't TDD the generated output.

Decision Factors

When the tree above doesn't give a clear answer, weigh these factors:

Factor Favors TDD Favors Test-After
Testability Clear inputs/outputs, deterministic Heavy side effects, UI rendering
Complexity Multiple branches, edge cases Straightforward single-path logic
Risk Failure is costly (data loss, security) Failure is cosmetic or low-impact
Stability Requirements are clear and stable Requirements are still changing
Team convention Team expects TDD Team doesn't practice TDD
Confidence You're unsure how to implement it You've built this exact thing before

Rule of thumb: If you're unsure, use TDD. The cost of writing a test first is low. The cost of a bug in untested code is high.


The TDD Cycle

1. RED    — Write a failing test that defines the desired behavior
2. GREEN  — Write the minimum code to make the test pass
3. REFACTOR — Clean up without changing behavior (tests still pass)
4. REPEAT — Next behavior

Common Mistakes

  • Writing too much test at once — Test one behavior per cycle
  • Writing implementation before the test fails — The failing test is the spec
  • Skipping refactor — Technical debt accumulates in GREEN if you don't clean up
  • Testing implementation details — Test what it does, not how it does it

Handling Legacy Code Without Tests

Legacy code (code without tests) requires a different entry point into TDD.

Step 1: Characterization Tests

Before changing anything, write tests that capture current behavior:

# Characterization test — documents what the code DOES, not what it SHOULD do
def test_calculate_total_current_behavior():
    result = calculate_total(items=[{"price": 10, "qty": 2}])
    assert result == 20  # Observed behavior, may or may not be correct

Step 2: Identify the Change Boundary

What's the smallest piece of code you need to change? Draw a boundary around it.

Step 3: Add Seams

If the code is untestable (hard dependencies, global state), add seams:

  • Extract method
  • Inject dependencies
  • Wrap external calls

Step 4: TDD the Change

Now that you have characterization tests protecting existing behavior and seams allowing isolation, use the normal RED-GREEN-REFACTOR cycle for your change.

Step 5: Decide What to Keep

After the change, decide which characterization tests to keep:

  • Keep tests that document important behavior
  • Replace tests that covered the code you changed (your TDD tests are better)
  • Remove tests that only existed to enable your refactoring

TDD by Test Type

Test Type TDD Approach
Unit tests Standard RED-GREEN-REFACTOR. One behavior per cycle.
Integration tests Write the test against the integration boundary first. May need stubs for external services during RED phase.
API tests Define the request and expected response first. Implement handler to make it pass.
E2E tests Not typically TDD'd per-cycle. Write E2E tests for critical paths after unit/integration TDD.

Quick Checklist

Before claiming a task is done with TDD:

  • Every production function has at least one test that was written before the function
  • No test was written after the code it tests (except characterization tests for legacy code)
  • All tests pass
  • Code has been refactored after going GREEN
  • Tests verify behavior, not implementation