Designing test cases
You can’t test every input. A shipping calculator that takes a weight between 0 and 30 kg has millions of possible values. Test design techniques pick the few that are most likely to reveal bugs.
Equivalence partitioning
Divide inputs into groups that the system should treat the same way, then test one value from each group.
For “weight must be more than 0 and at most 30 kg”:
| Partition | Example | Expected |
|---|---|---|
| Below range | -5 | rejected |
| Zero | 0 | rejected |
| Valid | 12.5 | price calculated |
| Above range | 31 | rejected |
| Not a number | “abc” | rejected |
If 12.5 works, 13 almost certainly works too, so testing both adds little.
Boundary value analysis
Bugs cluster at the edges of partitions, where someone wrote < instead of <=. Test on each side of every boundary:
0 -> rejected (lower boundary)
0.01 -> accepted (smallest valid value)
30 -> accepted (upper boundary)
30.01 -> rejected (just above)
Combine the two techniques: one value from the middle of each partition, plus both sides of each boundary.
Decision tables
When several conditions interact, list every combination in a table so none is forgotten. Example: a discount applies to members, or to orders over 100, but never to sale items.
| Member | Order > 100 | Sale item | Discount? |
|---|---|---|---|
| yes | no | no | yes |
| no | yes | no | yes |
| yes | yes | no | yes |
| no | no | no | no |
| yes or no | yes or no | yes | no |
Each row becomes a test case, and writing the table often reveals rules the specification never stated.
State transition testing
Some behaviour depends on history. An order moves through states: created -> paid -> shipped -> delivered, with cancelled possible before shipping. Test:
- every valid transition;
- important invalid ones: can a shipped order be cancelled? Can it be paid twice?
- sequences that revisit states, such as a payment that fails and is retried.
Drawing the state diagram first is often the fastest way to find gaps in the requirements.
Error guessing and exploratory testing
Structured techniques find a lot, and experience finds the rest. Common trouble spots include empty input, very long input, Unicode and emoji, leading and trailing spaces, time zones and daylight-saving changes, leap years, concurrent edits, and slow or failing networks.
Exploratory testing is time-boxed, focused investigation with a goal (“try to break checkout with unusual addresses”). Take notes, and turn the bugs you find into automated regression tests.
Writing the test itself
A good test case states:
- the precondition: what must be true beforehand;
- the action: exactly what is done;
- the expected result: specific and observable, such as “error message ‘Weight must be at most 30 kg’ appears”, rather than “it works”.
Name automated tests after the behaviour they check, for example rejects_weight_above_30_kg, so a failure tells you what broke without reading the code.