Tests as First-Class Citizens
Tests are what make code changeable — so the suite deserves the same care as the code it protects.
The idea
Every refactor you have done so far leaned on a safety net: a test suite that stayed green while you tore code apart. That net is the whole argument of this chapter. Without tests, every improvement is a gamble, so improvements stop happening, and the code rots. The dirtiest codebase is not the one with ugly functions — it is the one nobody dares touch.
Test-first is a discipline, not a vibe. The rules compress into three: no production code until a test fails, no more test than it takes to fail, no more code than it takes to pass. Followed literally you cycle every minute or two. The by-products are what matter — the code is always covered, a working state is always seconds behind you, and the design is shaped by its first caller.
The counterintuitive part is that dirty tests are worse than no tests. Duplicated setup, magic numbers and five unrelated assertions make a failure ambiguous: did the code break, or is the test just brittle? Eventually someone proposes deleting the suite, and now you have unchangeable code again. Test code is a first-class citizen; the only thing it may trade away is efficiency, never readability.
A clean test has a shape — build the world, perform one operation, check one result — and anything else is noise you push into helpers until the test reads like a sentence in your domain. Keep it Fast, Independent, Repeatable, Self-validating and Timely. Most of those fail silently: a suite passes for months while quietly becoming order-dependent, and you find out the day a harmless reordering breaks CI.
The bench — 3 exercises
TDD Under Oath
Build a discount-rules feature through six honest red-green-refactor cycles, one spec rule per cycle, never writing production code before a failing test. This is the Three Laws made mechanical instead of aspirational.
- Write down six incremental discount rules (bulk discount, loyalty tier, seasonal cap, stacking) as a small spec file before writing any code.
- For each rule, write one test first and run it — confirm it fails with a real assertion failure, not an import or collection error.
- Write the minimum production code to make that one test pass, then run the whole suite.
- Refactor both test and production code while green, then start the next cycle.
- Keep a short log per cycle recording the failing message you saw and the change that turned it green.
hint
Stuck on the first test? Take the degenerate case: no discounts apply, total unchanged. It forces the entry-point function into existence, which is the real first design decision.
hint
If a new test passes immediately, you wrote too much code in the previous cycle — back it out and shrink the step.
hint
Commit at each green point; the log becomes the ledger proving the test came first.
DONE WHEN
· Six commits or log entries, each showing a failing test before the code that fixed it
· Every red step failed on an assertion, not a missing import
· Full suite green after each cycle
· No production line in discounts/ that no test exercises
The Test Rehab
Take a suite of technically-passing but unreadable tests and refactor it into Build-Operate-Check with a tiny testing language of your own. Unreadable green tests hide their own lies — you will find one.
- Pick roughly fifteen ugly tests: duplicated setup blocks, magic expected values, several unrelated asserts each, names like test_calc_2.
- Rehab three of them by hand into build, operate, check — do not build helpers yet.
- Extract the duplication you just felt into a test_support builder layer, then apply it across the rest.
- Split multi-concept tests apart and rename each to state the rule it proves, such as bulk_discount_requires_ten_units.
- Replace every magic literal with a named constant or an expression derived from the inputs.
hint
Grow the testing language from duplication pain; designing it up front produces gold-plated helpers nobody uses.
hint
Multiple asserts on one result object still count as one concept — the rule is one concept, not literally one assert.
hint
When a derived expectation disagrees with the old magic number, trust the derivation and investigate: that test was asserting stale behavior.
DONE WHEN
· Every test reads as three visible phases and fits on a screen
· No setup block longer than three lines is duplicated across tests
· No unexplained numeric literal appears in an assertion
· Coverage of the target module is the same or higher than before
The F.I.R.S.T. Audit
Hunt the rot that green hides — order dependence, real clocks and leftover files — in a suite that passes today, on your machine, in default order. These are the failures that later look like nonsense in CI.
- Run each test file alone, then run the whole suite with a randomized order using several different seeds.
- Run the suite twice back to back without cleaning, to expose tests that leave residue behind.
- Fix each violation properly: inject a fake clock, isolate state per test, use temp directories with cleanup.
- Log every finding in an AUDIT.md as test, F.I.R.S.T. letter violated, root cause, fix.
hint
A test that fails alone but passes in the full suite is confessing that an earlier test does its setup for it.
hint
Anything reading the current time, the filesystem or a module-level mutable global is a suspect before you even run it.
hint
Make the fake clock an injected dependency, not a monkeypatch — that seam is reused when you isolate interfaces later.
DONE WHEN
· Suite green under five different random orderings
· Suite green when run twice consecutively with no cleanup between
· No test reads the real clock or module-level mutable state
· AUDIT.md lists at least three entries naming the violated letter
Go deeper (after the bench)
Read Clean Code chapter 9 now — it is short, and the dirty-then-clean test pairs land differently once you have produced your own before-and-after. Then read Martin Fowler's free "Eradicating Non-Determinism in Tests" (martinfowler.com/articles/nonDeterminism.html); exercise 03 was three entries from that catalog, and the rest is the field guide to flaky suites.