Writing Effective Tests

Difficulty
Importance

The problem

Manually re-checking that a change didn't break existing behavior doesn't scale past a tiny codebase, and relying on it means regressions slip through — automated tests encode 'this should still work' as executable checks that run in seconds, every time, catching regressions before a human ever notices.

Why now

Proof techniques already established that testing individual cases is evidence, not proof, of correctness for all inputs — tests don't escape that limit, but they're the practical, continuously re-checkable substitute for a full proof that real software relies on. Functions and scope already established that pure functions (same input, same output) are the easiest thing in the world to test, which is exactly why testable code and well-structured code overlap so heavily.

Mental model

A good test is a small, isolated proof-techniques-style case check: given this specific input, assert this specific output, and make the assertion fail loudly and clearly if it's ever violated. Pure functions are the easiest target — no setup, no mocking, just input and output — which is why code that's hard to test is very often also code with tangled, hidden dependencies worth refactoring anyway.

Requires

Unlocks

Used in

Projects

  • Write a thorough unit test suite for a pure function, covering typical inputs, edge cases, and invalid inputs
  • Take an untested piece of code entangled with side effects, refactor the core logic into a pure function, and write tests for it that wouldn't have been possible before the refactor

Examples

  • A test asserting add(2, 2) === 4 is trivial precisely because add is pure — no setup, no mocking, just input and expected output
  • A regression test written immediately after fixing a bug ensures that specific bug can never silently reappear without a test failing first

Resources

Mastery checklist