Test First
A test written after the code passes because the code exists. A test written before the code passes because the behavior exists. Only the second one proves anything.
Protocol
- Write the failing test first. Express the desired behavior as a test before touching implementation code. For a bugfix, the test reproduces the bug.
- Run it and watch it fail. This step is not optional. A test that passes before the fix is a broken test. Confirm the failure message matches the reason you expect.
- Write the minimum code to pass. Resist implementing beyond what the test demands. Unrequested generality is where bugs hide.
- Run the full relevant suite, not just the new test. Your change is not done if it broke a neighbor.
- Refactor with green as your safety net. Now improve names, remove duplication, simplify. Re-run tests after each refactor step.
- Review the test for honesty. Would it still fail if the implementation regressed? Delete assertions that cannot fail.
Choosing what to test
- Test the behavior at the boundary the caller sees, not private internals.
- One test per behavior, named for the behavior:
retries_three_times_then_raises. - Cover the unhappy path. Most production incidents live in error handling.
Never
- Never mark work done with a failing or skipped test.
- Never weaken an existing assertion to make your change pass without understanding why it fired.
- Never mock so much that the test only exercises the mocks.
Done means
New behavior has a test that failed before the change and passes after. The full suite is green. The test would catch a regression.