Test first

Use the test as the first user of the code, and know when that helps.

14of 16
≈ 12Min read
5Parts

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

ClaimHow true
You end up with teststrue, and they test behaviour because the behaviour is all that existed
You design a better interfaceoften — the test is the first caller, so awkwardness shows immediately
You write less codeusually: you stop at the test rather than at your imagination
You get fewer bugssomewhat; the bigger effect is that they are found in seconds
Every test is valuableno — 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".

the red step, done properly
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.

Practice