# Test-Driven Development (TDD)
## Philosophy
Use TDD cycles as your standard workflow to write code.
Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.
**Good tests** are integration-style: they exercise real code paths through public APIs. They describe _what_ the system does, not _how_ it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure.
**Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.
See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines.
## Anti-Pattern: Horizontal Slices
**DO NOT write all tests first, then all implementation.** This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code."
This produces **crap tests**:
- Tests written in bulk test *imagined* behavior, not *actual* behavior
- You end up testing the *shape* of things (data structures, function signatures) rather than user-facing behavior
- Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine
- You outrun your headlights, committing to test structure before understanding the implementation
**Correct approach**: Vertical slices via on tracer bullet at a time. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle. Because you just wrote the code, you know exactly what behavior matters and how to verify it.
```
WRONG (horizontal):
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
RIGHT (vertical):
RED→GREEN: test1→impl1
RED→GREEN: test2→impl2
RED→GREEN: test3→impl3
...
```
## Seams — where tests go
A **seam** is the public boundary you test at: the interface where you observe behavior without reaching inside. Tests live at seams, never against internals.
**Test only at pre-agreed seams.** Before writing any test, write down the seams under test and confirm them with the user. No test is written at an unconfirmed seam. You can't test everything — agreeing the seams up front is how testing effort lands on the critical paths and complex logic instead of every edge case.
Ask: "What's the public interface, and which seams should we test?"
## Workflow
Review the [@WORKFLOW.md](WORKFLOW.md) for a detailed workflow; in summary:
1. Planning: Ask: "What should the public interface look like? Which behaviors are most important to test?" **You can't test everything.** Focus testing effort on critical paths and complex logic, not every possible edge case.
2. Tracer Bullet: Write exactly ONE behavior or acceptance criteria that confirms ONE thing about the system. Typically, the tracer bullet will be a [Gherkin](Tech/Software%20Development/Skills/to-prd/Gherkin-syntax.md).
3. Red-Green Testing Loop: For each functionality or aspect needed to make your tracer bullet green.
4. Refactor: After all tests pass, look for [refactor candidates](refactoring.md). **Never refactor while RED.** Get to GREEN first.
5. Commit: All the changes you made once a test is green and the code is refactored.
## Checklist Per Red/Green Cycle
```
[ ] Test describes behavior, not implementation
[ ] Test uses public interface only
[ ] Test would survive internal refactor
[ ] Code is minimal for this test
[ ] No speculative features added
```
## Testing rules
- ONLY ASSERT WHAT THE TEST OR SCENARIO CLAIMS TO VALIDATE.
- IDEAL TESTS HAVE EXACTLY ONE ASSERTION, no less and no more.
- For example, if you are testing an HTTP endpoint is returning content type JSON, don't also assert the status code.
- Test the behavior of the public interface only: exported functions, methods, HTTP handlers, or CLI commands, not private helpers.
- Only write just enough code to make the the test or scenario pass.
- Use descriptive names and simple language so anyone can understand the intent without reading the code.
- Tests must be independent of each other and test business rules and requirements, not implementation logic.
- Mock external dependencies, not internal logic, and focus on behavior not implementation.
- Tests should run in milliseconds, to keep the test suite fast.
- Tests over a few dozen milliseconds should be set aside and not run every time (e.g., end-to-end integration and acceptance tests that get run only before frequent deployments).
- If you add new tests, review how redundant the setup is with other tests, and clean up the code.
- Only worry about the concrete aspects under test and keep test code DRY.