Red, green, refactor
The loop is minutes long, not hours. If a red step takes twenty minutes to write, the piece being tested is too big.
- Red — write one small failing test. Run it, and watch it fail for the reason you expect. A test that fails because of a typo has proved nothing.
- Green — write the least code that makes it pass. Ugly is allowed; the next step is where it gets tidy.
- Refactor — improve the code with the test still passing. This is the step people skip, and it is the one that pays.
What it actually buys
| Claim | How true |
|---|---|
| You end up with tests | true, and they test behaviour because the behaviour is all that existed |
| You design a better interface | often — the test is the first caller, so awkwardness shows immediately |
| You write less code | usually: you stop at the test rather than at your imagination |
| You get fewer bugs | somewhat; the bigger effect is that they are found in seconds |
| Every test is valuable | no — you can test-drive your way to a brittle suite as easily as any other |
Where it fits, and where it does not
It fits a function with a clear contract — parsing, pricing, validation, a state machine, a bug with a known reproduction. Anywhere the answer is knowable before the code exists.
It fits badly when you are exploring: a layout you are designing in the browser, an API you are learning, a spike to find out whether something is even possible. Explore first, then delete the spike and drive the real thing with tests. Writing tests for code you are about to throw away is not discipline, it is ceremony.
The discipline that matters most
Watching the failure is the whole point of the red step. It proves the test can fail, which is the property that most tests in a codebase have never been checked for — and the reason the mutation tester in the last lesson found holes in a suite with "full coverage".
it("takes ten per cent off above two thousand", () => {
expect(quote({ lines: [{ qty: 3, price: 890 }] })).toBe(2403);
});
// run it. Expected:
// expected undefined to be 2403 ← the function does not exist yet. Good.
// Not:
// ReferenceError: quoet is not defined ← a typo. The test proved nothing.
A useful habit even when you do not do it all day
For a bug: reproduce it as a failing test before looking at the code. You get the reproduction, the regression test and the definition of done in one step, and you cannot accidentally "fix" something that was never broken.