test-driven-development · git:20251129.c7223ee · 2025-11-29 · sha256 2998f45aaadbf296

test-driven-development git:20251129.c7223eeA

Immutable. This exact content is served forever at /api/v1/blob/2998f45aaadbf296.

---
name: test-driven-development
description: Core TDD philosophy and testing principles. Use when writing tests interactively or need testing guidance. Emphasizes tests as contracts, mock boundaries not internals, and implementation fixes over test changes.
---

# Test-Driven Development

**For system-level design principles and architectural boundaries, see the `software-engineer` skill.**

## Core Philosophy

<core_philosophy>
**Foundation**: TDD is an application of contract-based design. Tests define the contract an implementation must fulfill—they are specifications, not afterthoughts. This connects directly to the abstraction principles in the `software-engineer` skill.

**Tests = Contracts**: Tests represent business requirements and expected behavior. They are contracts the implementation must fulfill. Never compromise test integrity to achieve green tests.

**Tests are source of truth**: When tests fail, fix the implementation. Only change tests when requirements change or test has a verified bug.
</core_philosophy>

## Testing Approach

### Architectural Boundaries and Mocking Strategy

<architectural_boundaries>
**Identify architectural boundaries** in your system:
- Layer boundaries (e.g., data layer, service layer, presentation layer)
- Module boundaries (e.g., namespaces, packages, crates)
- Process boundaries (e.g., Erlang processes, Elixir GenServers)
- External system boundaries (e.g., APIs, databases, third-party services)
</architectural_boundaries>

<unit_definition>
**What constitutes a "unit":**

A "unit" is a cohesive component at an architectural boundary:
- Classes (e.g., Java, Python, Swift)
- Namespaces with related functions (e.g., Clojure, Haskell)
- Modules with related predicates (e.g., Prolog)
- Processes/GenServers (e.g., Erlang, Elixir)
- Modules with traits/protocols (e.g., Rust, Swift)
</unit_definition>

<mocking_rules>
**When to mock:**
- Mock dependencies **across** architectural boundaries
- External dependencies injected/passed into system under test get mocked

**When NOT to mock:**
- Don't mock the system under test itself
- Don't mock **within** a cohesive unit (e.g., private methods, internal helpers)
- If tempted to mock internals, that's a design smell — refactor instead
</mocking_rules>

<test_balance>
**Balance unit and integration tests:**
- **Unit tests**: Test each component in isolation with mocked dependencies across boundaries
- **Integration tests**: Test behavior across boundaries with real implementations
- Both are necessary — unit tests verify component logic, integration tests verify system behavior
- Over-mocked tests validate mocks, not real behavior
</test_balance>

<example>
**Layered data store (hexagonal architecture):**

```
┌───────────────────────────────────────────────────────┐
│                   Application Core                    │
│  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐  │
│  │   Domain    │   │   Service   │   │   Use Case  │  │
│  │   Models    │   │   Layer     │   │   Layer     │  │
│  └─────────────┘   └─────────────┘   └─────────────┘  │
│                                                       │
├───────────────────────BOUNDARY────────────────────────┤
│                                                       │
│  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐  │
│  │  Database   │   │   External  │   │   HTTP      │  │
│  │  Adapter    │   │   API       │   │   Client    │  │
│  └─────────────┘   └─────────────┘   └─────────────┘  │
└───────────────────────────────────────────────────────┘
```

- **Unit tests**: Test domain models and service layer with mocked adapters
- **Integration tests**: Test adapters with real external systems (DB, APIs)
- **Contract tests**: Verify adapters fulfill the port interface
- External database gets mocked at adapter boundary, not inside services
</example>

### Test Doubles Taxonomy

<test_doubles>
**Understanding test double types** aids in communicating test intent. This taxonomy was formalized by Gerard Meszaros in *xUnit Test Patterns* (2007):[^1]

| Type | Purpose | When to Use |
|------|---------|-------------|
| **Stub** | Returns predetermined values | When behavior doesn't affect test outcome |
| **Mock** | Verifies interactions occurred | When the interaction IS the behavior being tested |
| **Fake** | Working implementation (simplified) | When real implementation too slow/complex |
| **Spy** | Records calls for later verification | When need to verify after execution |

**Key insight**: Overuse of mocks often indicates testing implementation rather than behavior. Prefer stubs when possible; use mocks when interactions are the contract.

[^1]: Gerard Meszaros. 2007. *xUnit Test Patterns: Refactoring Test Code*. Addison-Wesley.
</test_doubles>

### Test Design Process

<design_steps>
**When designing tests:**
1. Identify architectural boundaries in the system
2. Determine what constitutes a "unit" (component, layer, module)
3. Plan unit tests for each unit with dependencies mocked at boundaries
4. Plan integration tests crossing boundaries with real implementations
5. Ensure tests verify usage/requirements, not implementation details
</design_steps>

<failure_response>
**When test fails:**
1. Read test to understand expected behavior
2. Debug implementation to find why it doesn't meet expectations
3. Fix implementation, never "fix" test to pass
</failure_response>

<quality_criteria>
**Test quality requirements:**
- Descriptive names explaining the requirement being checked
- Readable as documentation of system behavior
- Plan test cases based on usage, not implementation details
- Include comprehensive documentation (e.g., Javadoc for JUnit, Sphinx for Python) explaining what test ensures and special considerations
- Tests can have bugs or enforce wrong behaviors—always assess correctness
</quality_criteria>

## Test-Driven Development Workflow

<tdd_cycle>
1. **Write failing tests FIRST** based on requirements (including desired usage patterns)
   - Thoroughly document planned tests to guide implementation
   - Stub planned tests with exceptions: `throw new IllegalStateException("Not implemented yet!")`
2. **Implementation makes existing tests pass** without test modification
3. **Tests are source of truth** — never change tests to match broken code
4. **When debugging:**
   - Understand why test is failing
   - Fix implementation to match test expectations
   - Only modify tests if requirements changed or test has VERIFIED bug
5. **Treat test failures as implementation bugs**, not test bugs
   - Test bugs exist, but assume only after verifying implementation is correct
</tdd_cycle>

<debugging_tip>
**Debug and trace logging**: Effective way to probe program function during test execution.
</debugging_tip>

## Critical Anti-Patterns

<anti_patterns>
**Never:**
- Mock the system under test itself (the component being tested)
- Mock within a cohesive unit (e.g., private methods, internal helpers of the unit under test)
- Change test assertions to match current behavior
- Write tests that mirror implementation rather than requirements
- Over-mock to the point tests don't validate real behavior
- Write tests that don't exercise code in module being tested (e.g., calculating result in test instead of using module-under-test)
- Mock to make tests pass — fix implementation instead
</anti_patterns>

<design_smell>
**If tempted to mock internals of the unit under test**: That's a design smell — refactor the code instead.
</design_smell>

## Framework-Specific Guidance

**See language-specific skills** for testing framework details:
- **java-programmer**: JUnit, Mockito, assertion patterns
- **python-programmer**: pytest, unittest, mocking strategies
- **clojure-programmer**: clojure.test, test.check
- **racket-programmer**: RackUnit, testing patterns

General principles across frameworks:
- Use framework's assertion methods instead of custom comparison logic
- Include descriptive assertion messages explaining business/technical reasons
- Organize tests into logical categories/suites
- Maintain consistent assertion styles within codebase

## Planning and Scaffolding

<scaffolding_steps>
**Before implementing:**
- Create interfaces, type signatures, abstract classes, and other definitional structures
- Create empty functions with step-by-step comments: "1. Setup data.", "2. Process data.", etc.
- Write tests after defining but NOT implementing code structures to create example API uses
- Examine example code for potential usage problems requiring scaffold refactoring
</scaffolding_steps>

<maintainability_principles>
**Maintainability:**
- Assume code maintained by junior engineers and other LLMs
- Prefer strongly defined structures based on idiomatic patterns for the language
- If uncertain, ask — user is senior engineer with answers or resources
</maintainability_principles>

## Common Mistakes

<common_mistakes>
### Mechanical Mistakes
- Planning tests based on implementation instead of usage/requirements
- Creating tests that over-fit to specific implementation
- Removing existing documentation or comments (rephrase for consistency instead)
- Not documenting tests (every test needs comprehensive documentation)
- Using inconsistent assertion/verification styles across tests
- Not explaining business/technical reasons in assertion messages

### Judgment Mistakes (Staff-Level Insights)
- **Confusing "tests first" with "tests drive design"**: TDD is about using tests to discover design, not just writing tests before code
- **Treating coverage metrics as quality proxies**: 100% coverage with bad tests is worse than 70% coverage with good tests
- **Not recognizing test difficulty as design feedback**: If a unit is hard to test, the design is likely wrong—don't fight to mock, refactor instead
- **Writing tests that specify implementation sequence**: Tests should specify WHAT behavior occurs, not HOW or in what order
- **Testing internal state rather than observable behavior**: Tests should verify outputs and side effects, not implementation details
- **Over-mocking to achieve isolation**: If everything is mocked, the test validates mocks, not system behavior
</common_mistakes>

## Resources

<resources>
**Primary References:**
- Martin Fowler's testing articles: https://martinfowler.com/testing/
- Kent Beck's "Test-Driven Development: By Example" concepts
- Gerard Meszaros' test doubles taxonomy (xUnit Test Patterns)

**Key Principles:**
- Test Pyramid concept (Mike Cohn, 2009): more unit tests, fewer integration tests, even fewer E2E
- Arrange-Act-Assert pattern (Bill Wake, 2001) for test structure
- Given-When-Then (Dan North and Chris Matts, ~2006) for behavior specification

**See also:** Language-specific skills for framework-specific testing patterns (JUnit, pytest, clojure.test, RackUnit, PlUnit).
</resources>