Tests that earn their keep
A test is not proof that code is correct. It's a promise that this specific behavior won't change by accident.
- You can pick the right level of test for a change, and write one that fails loudly when the behavior breaks.
- You know why 100% coverage can still ship a broken feature.
Tests are how you buy the right to change code. Without them, every edit is a gamble and every refactor is a negotiation with fear. With them, the eighty-second test run answers the only question that matters after a change: did I break something that used to work?
6.1What to test first
- Business rules — pricing, permissions, state transitions. The logic that would embarrass you if it were wrong.
- Boundaries — zero, empty, maximum, duplicate, unauthorized, malformed. Most bugs live at the edges.
- Bugs you fixed — one test per bug, named after the bug. This is how a suite becomes a memory.
- Contracts with the outside world — the shape your code sends and accepts.
# the name is the specification — read it and you know what's expected
test_refund_requires_a_paid_order()
test_refund_rounds_to_cents_not_floats()
test_discount_cannot_exceed_order_total()
test_expired_token_returns_401_not_500()
6.2Which level does this belong to?
6.3Anatomy of a test that lasts
Nearly every durable test has the same three parts, and the discipline is keeping them separate: arrange the world, act once, assert the outcome. When a test needs five actions and nine assertions, a failure tells you almost nothing — you know something in a long story broke.
# arrange — one obvious setup
order = Order(items=[Item("book", 1299, qty=2)], status="paid")
# act — one call, the behavior under test
refund = refund_for(order)
# assert — the outcome that matters, not the internals
assert refund == 2598 # cents, not floats
assert refund >= 0
Two more properties separate a test that helps from one that hurts. It must be deterministic — same result every run, which means no real clock, no network, no shared database row, no ordering assumptions. And it should have one reason to fail, so the red bar tells you what broke before you open the file.
A test that fails intermittently is not "annoying" — it is a bug in the test, and its real cost is cultural: the team learns that red means "run it again". Fix it, quarantine it with a ticket, or delete it. Never leave it in the suite and hope.
6.4Red, green, refactor
Test-driven development is one loop, and it is worth trying at least once even if you don't adopt it fully: write the test first and watch it fail (red), write the smallest code that passes (green), then clean up while it stays green. The non-obvious benefit is design pressure — a rule that is painful to test is usually a function that is doing too much, or reaching for the database when it shouldn't.
A test that asserts your fake returns what you told it to return proves nothing about production. The other traps are just as quiet: tests that pass whether or not the code works, tests that fail randomly (delete or fix them — a flaky suite trains people to ignore red), and coverage treated as a goal. Coverage measures lines executed, not behavior verified.
Which unit test is worth keeping?
60-second recap
- Mostly fast tests, few slow ones. 70/20/10 is a shape, not a law.
- Test rules, boundaries, fixed bugs and contracts — not getters and framework behavior.
- Name tests as sentences. The suite becomes the documentation that stays true.
- Coverage is not correctness. A flaky test is worse than no test.