THIS EXPLANATION
THE ROOM
CDA·272 Computing, Data & AI 6 MIN · 8 STATIONS

Writing the test first

A Socratic walk-through of writing the test first — reasoned out one step at a time, not lectured.

abcdefgh
a

The question we started with

THE QUESTION #

Why write a test for code that does not exist yet?

The instruction sounds like a prank. Write a test for a function that does not exist. Run it. Watch it fail — obviously, since you are calling something that is not there. Only then write the thing.

If the point of a test is to check whether code works, this ordering is absurd: you cannot check work that has not been done. So either the practice is a superstition that survived on charisma, or a test does something besides checking. Let us assume the second and find out what.

b

Reasoning it through

REASONING #

Begin with a small observation. To write a test for a function, you must first decide what to call it, what to hand it, and what it should give back — you cannot write the assertion without committing to the answer's shape. So before a line of implementation exists you have made three design decisions, and you made them from the caller's side of the boundary, which is the only side that ever matters to anyone else.

Compare the usual order. Writing the implementation first, you decide those things too, but while looking at the inside: what data you happen to have, what the loop is already doing, what would be convenient to return from where you stand. The interface ends up shaped by the implementation's convenience. Does that produce an awkward function to call? Constantly — and by then it is written, so it stays.

So the ordering buys a change of vantage point, enforced by mechanics rather than discipline. It does not ask you to remember the caller; it makes proceeding without them impossible.

A second effect is subtler. What does a hard-to-write test tell you? Suppose testing one small behaviour requires constructing a database connection, a clock, a configuration object, and a logger. You have learned something real — not about the test but about the code you were about to write: that behaviour is entangled with four things it has no business knowing. The test was not being difficult; it was reporting a fact about the design, early enough to act on. Testability and good decoupling are largely one property seen from two angles, and that is the strongest argument for the practice.

The third is about failure. Why insist on watching the test fail? Because a test that has never failed has not been shown to be capable of failing. Assertions that quietly test nothing are common — a typo in the name so the runner skips it, an assertion inside a branch never reached, a mock that swallows the call. A test you saw go red for the expected reason and then go green has been calibrated. One green from birth is an untested instrument.

Notice what has happened to the word "test". Only the third of those effects is about verification; the first two are about design, and they are collected before any verification occurs. Verification is a by-product — which is why several of the practice's own advocates, Kent Beck among them, have said the name is a misnomer and "example-first design" would describe it better.

One more piece: the size of the loop. The discipline is not "write all the tests, then all the code" but one small failing test, the least code that passes it, then tidy up — often in minutes. Why so small? Because feedback is only useful while the change that caused it is still small enough to attribute. Write forty minutes of code and something breaks: you have a search problem. Write ninety seconds of code and something breaks: you have a memory.

An honest caveat: whether the practice produces measurably better outcomes is genuinely unsettled. Controlled studies, including a well-known multi-site experiment by Erdogmus and colleagues and later replications, give mixed results, and it is very hard to separate the effect of the ordering from the effect of simply writing more tests. The reasoning above describes real mechanisms; it does not license the claim that the evidence is decisive.

c

The analogy

THE ANALOGY #
THE FIGURE

It is like writing the label on a jar before filling it. You must decide what goes in, in what quantity, and what you would call it if a stranger opened the cupboard — all of it while thinking about the stranger, not about what is left on the counter. Fill first and label after, and the label describes whatever you happened to have, which is why cupboards fill with jars called "mixed dry things".

WHERE IT BREAKS DOWN

a jar's label cannot tell you the contents were wrong, whereas the test pushes back — if the label is genuinely unwriteable, that is information about the recipe, and no jar ever refused to be filled.

d

Clarifying the model

THE MODEL #

Two misconceptions are worth heading off. The first is that test-first means designing everything up front. It is nearly the opposite: design is deferred to the last responsible moment and taken one example at a time. You are not specifying the module, you are specifying the next behaviour, and the module's shape emerges from a run of those decisions. That is why "refactor" is not optional housekeeping — it is where accumulated small decisions get reorganised into something coherent. A team that does red and green but skips refactor has kept the cost and dropped half the benefit.

The second is that the practice is a universal law. It fits best where you know what you want and are unsure how to build it, and badly where you do not yet know what you want — exploratory work, a spike against an unfamiliar API, a layout you will judge by looking at it. Writing a test first for behaviour you are still discovering means committing to an answer you have not got. Test-first converts a decided intention into a design; when the intention is not decided, it has nothing to work on.

A last refinement: the failing test is a commitment device. Once it exists and is red, "I will add a test later" stops being available, because the alternative — deleting a test to avoid writing code — is something you would have to do deliberately and could not call an oversight.

e

A picture of it

THE PICTURE #
Writing the test first
Writing the test first Start at the entry point and enter Red, where a test exists for behaviour that does not. The self-loop on Red is the calibration check -- if it fails for an unintended reason, the test is wrong and gets fixed before any production code is written. The edge to Green is deliberately labelled "the least code", because writing more than the example demands is how the loop stops being small. Refactor is the only state where the design changes without the behaviour changing, and its back-edge to Red is the whole cycle: each pass adds one example, not one module. {"generator":"mermaid-svg-renderer@3.2.1","source":"../Socrates/.diagram-cache/_src/writing-the-test-first.md","sourceIndex":1,"sourceLine":4,"sourceHash":"71f203d9574d096b95fe4edacb6a29af9cb5d77b6c10c2b763a88303acc8bc0f","diagramType":"stateDiagram","layoutVariant":"source","repairedDuplicateIds":[],"motion":"entrance-with-reduced-motion-fallback","presentation":"editorial","attempt":1,"viewBox":{"x":0,"y":0,"width":900,"height":808},"qa":{"passed":true,"findings":[]}} write only enough tosatisfy it behaviour is now pinned next behaviour, nextexample fails for the wrong reason,fix the test the feature is done Red -- a failing example Green -- the least code thatpasses Refactor -- tidy with the net up Watching it fail is whatcalibrates the instrument
KINDSconnectorpositive branch

How to readStart at the entry point and enter Red, where a test exists for behaviour that does not. The self-loop on Red is the calibration check — if it fails for an unintended reason, the test is wrong and gets fixed before any production code is written. The edge to Green is deliberately labelled "the least code", because writing more than the example demands is how the loop stops being small. Refactor is the only state where the design changes without the behaviour changing, and its back-edge to Red is the whole cycle: each pass adds one example, not one module.

f

What became clearer

WHAT CLEARED #
WHAT CLEARED

Writing the test first is only incidentally about testing. It forces three things to happen early that otherwise happen late or not at all: the interface gets designed from the outside, the coupling gets reported before it is paid for, and the test itself gets shown to work. The failing state is the calibration step, not a formality, and the small loop is what keeps every failure attributable. Whether it produces measurably better software than writing thorough tests immediately afterwards remains genuinely open; what it reliably changes is when you find out your design is awkward.

g

Where to go next

ONWARD #
  • How outside-in and inside-out variants of the practice lead to different module boundaries.
  • Whether property-based tests can serve as the first test, given that they specify a law rather than an example.
h

Key terms

TERMS #
TermWhat it means
Test-driven developmentwriting a failing test, making it pass with minimal code, then refactoring, repeated in short cycles.
Red-green-refactorthe three states of that cycle, named for the test runner's colours.
Design pressurethe tendency of a difficult-to-write test to reveal unwanted coupling in the code being tested.
Spikea short exploratory piece of code written to learn something, usually thrown away, and normally exempt from test-first discipline.

Every term the collection defines is gathered in the glossary.

Nearby on the shelf

4