Test-Driven Development
Use TDD to specify behavior with executable tests before relying on an
implementation. Be disciplined when behavior is important, but proportionate:
strict Red-Green-Refactor is not required for every mechanical or exploratory
change.
When to Use
Use TDD when:
- Adding or changing production behavior.
- Fixing a bug that can be reproduced with a regression test.
- Implementing domain rules, parsers, validation, permissions, state machines,
or error handling.
- Refactoring risky code where tests can preserve behavior.
- Clarifying an API or contract through examples.
Use lighter test-first or test-after work when:
- Spiking unknown APIs, investigating feasibility, or doing throwaway
exploration.
- Making documentation-only, formatting-only, generated, or mechanical changes.
- Updating code that is already strongly covered and where a new test would only
duplicate existing evidence.
When implementation begins, load the matching language engineering or test skill
for repository-specific execution and validation.
For photo/video DAM behavior, compose with
digital-asset-management for media
fixtures and the distinction among originals, edit recipes, renditions, and
asset versions.
Red-Green-Refactor
Red: write the smallest meaningful failing test.
- Name the behavior being specified.
- Assert externally relevant outcomes, not private implementation details.
- For bugs, make the test fail for the observed defect before fixing it.
- Confirm the failure is for the expected reason when practical.
Green: make the test pass with the smallest correct change.
- Implement only enough behavior to satisfy the current test and nearby
obvious invariants.
- Avoid broad refactors while tests are failing.
- Keep the code understandable; "smallest" does not mean intentionally bad.
Refactor: improve design after tests pass.
- Remove duplication, clarify names, and simplify structure while preserving
behavior.
- Run the relevant tests after each meaningful refactor step.
- Stop when the design is clear enough for the current scope.
Repeat the cycle for each behavior slice.
Choosing the Test Level
- Unit: pure logic, domain rules, parsing, formatting, validation, edge cases.
- Integration: interactions with databases, filesystems, queues, external
boundaries, framework wiring, or multiple modules.
- Acceptance: business-readable behavior or product workflows.
- Contract: API, message, schema, plugin, or service-boundary compatibility.
- Regression: a previously observed bug that must not return.
- Property: invariants across many generated inputs.
- End-to-end: user journeys where the full stack or browser behavior is the
point of confidence.
Prefer the narrowest level that gives trustworthy feedback. Add broader tests
only when narrower tests cannot prove the behavior that matters.
TDD With Architecture Boundaries
Load hexagonal-architecture when test
design depends on ports, adapters, external actors, or infrastructure-independent
domain behavior. Load
clean-architecture for use-case,
interactor, presenter, and interface-adapter test boundaries. Load
onion-architecture when tests should protect
domain/application rings around a domain model.
- Drive domain entities, value objects, aggregates, and domain services with
narrow unit tests.
- Drive application services or use cases with fake or in-memory outbound
adapters so workflows can be tested without real databases, brokers, SDKs, or
clocks.
- Add contract tests when multiple adapters must satisfy the same port.
- Test real adapters with integration tests against the target framework,
database, filesystem, message broker, or external API contract.
- Avoid over-mocking the core; substitute external boundaries, not the domain
behavior being specified.
Test Quality Rules
- Test behavior, not private methods, incidental call order, or internal data
shapes unless those are the contract.
- Keep tests fast, deterministic, isolated, and readable.
- Avoid excessive mocking; mock slow or external boundaries, not the domain you
are trying to specify.
- Use realistic fixtures sparingly and keep them understandable.
- Assert meaningful outcomes and important side effects.
- Cover edge cases that encode real rules, not arbitrary permutations.
- Keep test names specific enough to explain the behavior when they fail.
Completion Rules
Before considering work complete:
- The new or changed tests pass.
- Related existing tests still pass or failures are explained as pre-existing or
intentionally out of scope.
- Compilation, typechecking, linting, formatting, and other verification failures
introduced by the change are resolved.
- Temporary debug output, skipped tests, weakened assertions, and test-only hacks
are removed unless explicitly justified.
Do not claim a fix works because the code looks right; report the verification
that actually ran.
Common Pitfalls
- Writing a large test after implementation and calling it TDD.
- Over-mocking collaborators so the test proves the mock setup rather than the
behavior.
- Freezing implementation details that should remain refactorable.
- Refactoring while tests are red and losing the reason for failure.
- Ignoring failing lint, type, build, or unrelated test signals that may indicate
the change is incomplete.
Zod Test Routing
For selected Zod parsing, load zod-engineering. Test actual sync/async parser boundaries, input/output divergence, transforms/coercion, and stable mapped codes/paths—not types or default wording.
1---2name: test-driven-development3description: Apply Test-Driven Development to implementation and bug fixes. Use when adding or changing behavior, fixing defects, writing regression tests, choosing test levels during implementation, or using Red-Green-Refactor to guide autonomous coding work. Do not use for risk-focused reviews of an existing test suite or plan; use testing-strategy.4---56# Test-Driven Development78Use TDD to specify behavior with executable tests before relying on an9implementation. Be disciplined when behavior is important, but proportionate:10strict Red-Green-Refactor is not required for every mechanical or exploratory11change.1213## When to Use1415Use TDD when:1617- Adding or changing production behavior.18- Fixing a bug that can be reproduced with a regression test.19- Implementing domain rules, parsers, validation, permissions, state machines,20 or error handling.21- Refactoring risky code where tests can preserve behavior.22- Clarifying an API or contract through examples.2324Use lighter test-first or test-after work when:2526- Spiking unknown APIs, investigating feasibility, or doing throwaway27 exploration.28- Making documentation-only, formatting-only, generated, or mechanical changes.29- Updating code that is already strongly covered and where a new test would only30 duplicate existing evidence.3132When implementation begins, load the matching language engineering or test skill33for repository-specific execution and validation.3435For photo/video DAM behavior, compose with36[`digital-asset-management`](../digital-asset-management/SKILL.md) for media37fixtures and the distinction among originals, edit recipes, renditions, and38asset versions.3940## Red-Green-Refactor41421. Red: write the smallest meaningful failing test.43 - Name the behavior being specified.44 - Assert externally relevant outcomes, not private implementation details.45 - For bugs, make the test fail for the observed defect before fixing it.46 - Confirm the failure is for the expected reason when practical.47482. Green: make the test pass with the smallest correct change.49 - Implement only enough behavior to satisfy the current test and nearby50 obvious invariants.51 - Avoid broad refactors while tests are failing.52 - Keep the code understandable; "smallest" does not mean intentionally bad.53543. Refactor: improve design after tests pass.55 - Remove duplication, clarify names, and simplify structure while preserving56 behavior.57 - Run the relevant tests after each meaningful refactor step.58 - Stop when the design is clear enough for the current scope.5960Repeat the cycle for each behavior slice.6162## Choosing the Test Level6364- Unit: pure logic, domain rules, parsing, formatting, validation, edge cases.65- Integration: interactions with databases, filesystems, queues, external66 boundaries, framework wiring, or multiple modules.67- Acceptance: business-readable behavior or product workflows.68- Contract: API, message, schema, plugin, or service-boundary compatibility.69- Regression: a previously observed bug that must not return.70- Property: invariants across many generated inputs.71- End-to-end: user journeys where the full stack or browser behavior is the72 point of confidence.7374Prefer the narrowest level that gives trustworthy feedback. Add broader tests75only when narrower tests cannot prove the behavior that matters.7677## TDD With Architecture Boundaries7879Load [`hexagonal-architecture`](../hexagonal-architecture/SKILL.md) when test80design depends on ports, adapters, external actors, or infrastructure-independent81domain behavior. Load82[`clean-architecture`](../clean-architecture/SKILL.md) for use-case,83interactor, presenter, and interface-adapter test boundaries. Load84[`onion-architecture`](../onion-architecture/SKILL.md) when tests should protect85domain/application rings around a domain model.8687- Drive domain entities, value objects, aggregates, and domain services with88 narrow unit tests.89- Drive application services or use cases with fake or in-memory outbound90 adapters so workflows can be tested without real databases, brokers, SDKs, or91 clocks.92- Add contract tests when multiple adapters must satisfy the same port.93- Test real adapters with integration tests against the target framework,94 database, filesystem, message broker, or external API contract.95- Avoid over-mocking the core; substitute external boundaries, not the domain96 behavior being specified.9798## Test Quality Rules99100- Test behavior, not private methods, incidental call order, or internal data101 shapes unless those are the contract.102- Keep tests fast, deterministic, isolated, and readable.103- Avoid excessive mocking; mock slow or external boundaries, not the domain you104 are trying to specify.105- Use realistic fixtures sparingly and keep them understandable.106- Assert meaningful outcomes and important side effects.107- Cover edge cases that encode real rules, not arbitrary permutations.108- Keep test names specific enough to explain the behavior when they fail.109110## Completion Rules111112Before considering work complete:113114- The new or changed tests pass.115- Related existing tests still pass or failures are explained as pre-existing or116 intentionally out of scope.117- Compilation, typechecking, linting, formatting, and other verification failures118 introduced by the change are resolved.119- Temporary debug output, skipped tests, weakened assertions, and test-only hacks120 are removed unless explicitly justified.121122Do not claim a fix works because the code looks right; report the verification123that actually ran.124125## Common Pitfalls126127- Writing a large test after implementation and calling it TDD.128- Over-mocking collaborators so the test proves the mock setup rather than the129 behavior.130- Freezing implementation details that should remain refactorable.131- Refactoring while tests are red and losing the reason for failure.132- Ignoring failing lint, type, build, or unrelated test signals that may indicate133 the change is incomplete.134135## Zod Test Routing136137For selected Zod parsing, load [`zod-engineering`](../zod-engineering/SKILL.md). Test actual sync/async parser boundaries, input/output divergence, transforms/coercion, and stable mapped codes/paths—not types or default wording.