Writing Effective Tests
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
- Functions & ScopeProgramming Fundamentals
The easiest and most valuable tests target pure functions — input in, output out, no hidden state; you need the function/scope model already established to recognize which code is naturally testable and which needs restructuring first.
- Error HandlingProgramming Fundamentals
Testing isn't just checking the happy path — verifying that a function fails correctly on bad input is a direct, deliberate application of the error-handling patterns already covered, now treated as behavior worth asserting on.
Unlocks
Used in
- Unit tests, integration tests, and end-to-end tests at every layer of a real system
- Test-driven development, writing a failing test before the code that makes it pass
- CI pipelines that block merging on test failure
- Regression tests written specifically to prevent a fixed bug from reoccurring
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
- I can write unit tests covering typical, edge-case, and invalid inputs
- I can explain why pure functions are easier to test than functions with hidden side effects
- I can write a regression test for a bug immediately after fixing it
- I can explain why passing tests are evidence, not proof, of correctness — and why that's still valuable