TDD is not optional. Code without tests is a liability.
| Type | What | When |
|---|---|---|
| Unit | Individual functions, utilities | Every function |
| Integration | API endpoints, DB operations | Every endpoint |
| E2E | Critical user flows | Every release |
Mandatory workflow for every feature and bug fix:
1. Write test β should FAIL (RED)
2. Run test β confirm it fails
3. Write minimal code β should PASS (GREEN)
4. Run test β confirm it passes
5. Refactor β improve code quality
6. Run all tests β nothing broke
7. Check coverage β 80%+
Why this order matters:
- Writing tests first forces you to think about the interface before the implementation
- A test that passes before you write code doesn't test anything
- Refactoring with tests is safe; without tests it's gambling
- Test behavior, not implementation
- One assertion per test (or one logical group)
- Tests must be independent (no shared state between tests)
- Tests must be deterministic (no flaky tests in CI)
- Mock external dependencies, not internal logic
- Name tests like sentences:
should return 404 when user not found
- Read the error message β it usually tells you what's wrong
- Check test isolation β is shared state leaking?
- Verify mocks are correct β stale mocks cause phantom failures
- Fix the implementation, not the test (unless the test is wrong)
- If you need to change a test, explain why in the commit message