Verdict
No
Do not write automated tests for throwaway code that only you run and then delete; check its output against one case worked out by hand, and add tests on the day someone decides to keep the code.
Why
- Blast radiusnone
- Change frequencyonce
- Detectabilityimmediately
- Reversibilitytrivial
- Test costmoderate
I do not write automated tests for throwaway code: a spike, a script that answers one question, or a puzzle solution that I run for a day and delete. Blast radius is none, because only I read the output. Change frequency is once: nobody changes the file after that day. Detectability is immediately and Reversibility is trivial, since I read each result as it prints and a bad run leaves nothing behind. Test cost is moderate, because a scratch folder has no test runner and the first test takes about an hour, and rule R1 gives Do not test.
| When | Decision | Why |
|---|---|---|
| The number a throwaway script prints goes into a team decision | Do not write a test; check the number against a second count from another query | Blast radius rises to internal and Detectability to eventually, but Change frequency stays once, so rule R7 still gives Do not test |
| The throwaway script changes production data once, such as a bulk fix of addresses | Test it differently: run it on a copy of the data, diff the result, keep a backup, and have a second person review it | Blast radius rises to users and Reversibility to with-effort: wrong rows need a repair script |
| The throwaway script issues refunds or payouts once | Test mandatory: test the amount for each kind of row before the run, then rehearse it on a copy | Blast radius rises to money and Reversibility to impossible: money paid out does not come back |
| A colleague keeps the script and edits it monthly for a team report | Test minimally: one test of the main calculation on a small table worked out by hand | Blast radius rises to internal, Change frequency to regularly and Detectability to eventually: a wrong total looks plausible |
| A spike ships in the product as the nightly import | Test the main path and the likeliest edge cases in CI before the code runs for users | Blast radius rises to users, Change frequency to regularly, Detectability to eventually and Reversibility to with-effort |
| The task comes with worked examples, so a test takes minutes | Do not test to protect the code; put the examples you would recheck by hand in a test file | Test cost falls to trivial, but Blast radius stays none, so rule R1 still gives Do not test |
What breaks if you don't test
While the code stays throwaway, a bug costs me one rerun. The damage starts when the code stops being throwaway and nobody says so: a colleague schedules the script, or its output becomes the number in a planning meeting. A join that drops customers with two addresses then undercounts without an error.
What you lose if you over-test
In my practice, a script that takes 40 minutes to write costs more than twice as much once I add an hour of test setup. Tests also make a script look finished, and code that looks finished is the code that people keep.
What to do instead
- Before you act on the output, compare it with one case worked out by hand or a second count.
- Write "throwaway" and the date on the first line of the file, and keep it outside the main repository.
- If you recheck the same example after every edit, put it in a pytest or Deno test that reruns on every save.
- On the day someone keeps the code, write a test of its main path before it runs again.
Procedure and references
When the answer changes
- The code writes to data that other people use, or moves money.
- Someone other than you runs it, or runs it again next month.
- Its output goes into a decision with no second count behind it.
Counterexample + Code example
Advent of Code, where the tests came first
Bruce Lewis argues the opposite in In praise of unit tests for throwaway code, using the Advent of Code 2023 puzzle for day 7, which ranks poker hands. The puzzle gives five sample hands with total winnings of 6440, and he turned them into tests that rerun on every save. When the second part of the puzzle changed the rules for jacks, the tests let him change the ranking code instead of copying it.
Blast radius stays none and Change frequency stays once, so the risk did not move. Test cost did: it fell to trivial, below the cost of the manual check it replaces. The framework does not count a test that saves work while you write, so I write this kind whenever a task arrives with worked examples:
import { assertEquals } from "jsr:@std/assert";
import { totalWinnings } from "./day07.ts";
const sample = `32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483`;
Deno.test("sample from the puzzle text", () => {
assertEquals(totalWinnings(sample), 6440);
});
Related questions
FAQ
- Should I write unit tests for a spike?
No, a spike that answers one technical question and is then deleted needs no unit tests, because nobody runs its code again. Write the production version with tests.
- Should I test a one-off script?
A one-off script that only reads data and prints a result for you needs no automated test; compare its output with one case worked out by hand. A one-off script that changes shared data needs a rehearsal on a copy, and one that moves money needs tests.
- When does throwaway code need tests?
Throwaway code needs tests on the day someone keeps it: when it runs on a schedule, ships in the product, or gets a second user. Start with the main path.
- Are unit tests worth it for Advent of Code?
Unit tests for an Advent of Code puzzle pay off when the puzzle text gives worked examples, because a test file reruns them faster than you can by hand. Once the puzzle is solved, the tests protect nothing.