Writing code is only half the job; the other half is gaining confidence that it is correct. Testing does this by running the program on chosen inputs and comparing its actual output against the expected output you worked out independently.
A single test case is a pair: an input, and the output it should produce. A useful test suite covers three kinds of input:
| Input Type | Description & Examples |
|---|---|
| Typical cases | Ordinary, representative inputs (does double(4) give 8?) |
| Edge cases | Boundaries where bugs hide: zero, empty lists, extremes, negative numbers |
| Invalid cases | Inputs the program should reject or handle gracefully |
The mindset is adversarial: a good tester actively tries to break the program, not just confirm it works. Passing a hundred typical cases proves little if an overlooked edge case still crashes it.
Limits and Automation
Testing famously cannot prove a program correct because you cannot try every possible input. However, a well-chosen suite can reveal the presence of bugs cheaply and repeatably.
Automated tests are especially valuable because they can be re-run after every change. A regression test confirms that nothing which used to work has broken during updates.
Common pitfall: Testing only happy-path typical inputs and skipping edge cases. Most real bugs surface at boundaries like zeros, empty lists, or off-by-one indices. A test suite without edge cases gives false confidence; deliberately probe the boundaries.