3.8 KiB
| name | description |
|---|---|
| test-driven-development | Write the failing test before the implementation, for every feature or bugfix - red, green, refactor. Use before writing any implementation code, and when fixing a bug (reproduce it as a failing test first). Covers what makes a good test, why the order matters, the rationalizations that mean you are skipping it, and a verification checklist before marking work complete. Triggers: add a feature, fix this bug, implement X, write tests, TDD. |
Write the test first. Watch it fail. Write minimal code to pass.
Core principle: if you didn't watch the test fail, you don't know if it tests the right thing.
The Iron Law: NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST. Wrote code before the test? Delete it — completely, don't keep it "as reference" — and start over from tests.
Always use for: new features, bug fixes, refactoring, behavior changes. Exceptions (ask the user first): throwaway prototypes, generated code, configuration files.
Red-Green-Refactor Cycle
RED — Write a failing test. One minimal test showing what should happen. One behavior, clear name, real code (avoid mocks unless unavoidable).
Verify RED — watch it fail (mandatory, never skip). Run just that test. Confirm: it fails (not errors), the failure message is what you'd expect, and it fails because the feature is missing — not because of a typo. If it passes, you're testing existing behavior — fix the test. If it errors, fix the error and re-run until it fails correctly.
GREEN — write the minimal code to pass. Don't add extra features, options, or "improvements" beyond what the test requires (YAGNI).
Verify GREEN — watch it pass (mandatory). Run the test — and the full suite. Confirm it passes, no other tests broke, and output is clean (no errors/warnings). If it fails, fix the code, not the test.
REFACTOR — clean up while green. Remove duplication, improve names, extract helpers. Don't add new behavior. Keep tests green throughout.
Repeat for the next behavior.
Good Tests
Minimal (one thing — "and" in the name means split it), clear (name describes behavior, not "test1"), and shows intent (demonstrates the desired API).
Why Order Matters
Tests written after code pass immediately — which proves nothing (might test the wrong thing, might test implementation not behavior, might miss edge cases you forgot). Test-first forces you to watch it fail, proving it actually tests something. Manual testing is ad-hoc — no record, can't re-run, easy to forget cases under pressure.
Common Rationalizations (all mean: stop, do TDD properly)
"Too simple to test" / "I'll test after" / "Already manually tested" / "Deleting X hours of work is wasteful" (sunk cost — keeping unverified code is technical debt) / "Keep as reference, write tests first" (you'll adapt it — that's testing after) / "Need to explore first" (fine — throw away exploration, start clean with TDD) / "Test is hard to write = design is unclear, but I'll push through anyway" (listen to the test) / "TDD will slow me down" (it's faster than debugging after).
Verification Checklist (before marking work complete)
- Every new function/method has a test
- You watched each test fail before implementing, for the expected reason
- Wrote minimal code to pass each test
- All tests pass, output pristine
- Tests use real code (mocks only if unavoidable)
- Edge cases and errors covered
Can't check all boxes? You skipped TDD — start over.
Debugging Integration
Bug found? Write a failing test reproducing it first, then follow the Red-Green-Refactor cycle. The test proves the fix and prevents regression. Never fix bugs without a test.
Final Rule
Production code → a test exists and failed first. Otherwise, it's not TDD. No exceptions without explicit user permission.