This is the whole thing: the legacy calculator, six behaviours pinned by running it, a clean refactor, and the equivalence table proving the output stayed byte-for-byte identical. The exercise is this same page with three wrong pins and an empty refactor — you supply both.
A characterization test — a check that captures what the code actually does — does not argue with the code, it records it. Each recorded value becomes a spec the code must keep meeting. Below, every pinned value was produced by running LegacyTotal; only then was the refactor (tidying the code without changing its behaviour) allowed to start, and the pins are what prove it changed nothing.
The battery — what the legacy code actually prints
| Basket | Gross | Legacy prints | Why it surprises |
|---|---|---|---|
| (empty) | 0 | 0.00 |
the freebie |
| 1 × 800 | 800 | 800.00 |
under every threshold |
| 1 × 1000 | 1000 | 1,000.00 |
> is strict — exactly 1000 gets nothing |
| Laptop + 3 mice | 1275 | 1,211.25 |
5% once: 1275 × 0.95 |
| 2 × 1050 | 2100 | 1,995.00 |
the trap — the 2% test reads the already-discounted total (1995), not the gross (2100), so it never fires |
| 3 × 1000 | 3000 | 2,793.00 |
both fire sequentially: ×0.95 then ×0.98 — not a flat 7% (that would be 2790) |
The fifth row is the whole lesson. Read the legacy code top to bottom and the second if looks like "orders over 2000 get another 2%". Run it and you learn the truth: it reads x after the first discount already fired. Nobody decided that — it fell out of reusing one variable. It ships today, so the refactor must keep it.
The refactor, live and identical
Same behaviour, said clearly: named constants, one method, and a comment on the one line where the surprise lives. Press Run — six rows, three columns in agreement, all ✓.
Take the legacy basket-total calculator with its two sequential discount thresholds, pin its six characterized behaviours — including the trap where the 2% test reads the already-discounted total — then refactor it into named constants and one clear method without changing any of them. Run the battery showing pinned, legacy, and refactored values byte-identical on every row.
Notice what the refactor did not do: it did not "fix" the strict >, did not make the discounts additive, did not re-read the gross for the second threshold. Every one of those would be a rewrite — a behaviour change smuggled in under the word refactoring — and the battery would have caught it with a ✗ before any customer did.
← Back to the exercise · Module 1 — Introduction · All solutions