Tests as examples
Build a small HTTP service with clear boundaries, tests, logs and a repeatable runtime.
THE IDEA
A test is an example with a verdict
A test is a tiny program that calls your code with a specific input and states what the result must be. It is an example that can fail. Written before or after the code, a good set of tests is also a specification: anyone can read them and learn what the function promises, including the awkward corners.
def test_average_of_two():
assert average([1, 2]) == 1.5
# if average used // instead of /, pytest reports roughly:
> assert average([1, 2]) == 1.5
E assert 1 == 1.5
E + where 1 = average([1, 2])A failing test message should tell you three things: which example, what you expected, and what you got. That is why tests with a clear name and one idea each are easier to act on than one giant test.
Easy examples miss bugs
Imagine average(values) must return the mean as a float, and None for an empty list (there is no mean of nothing, and dividing by a count of zero is an error). Below are seven broken copies, each with one realistic bug. A test only has value if some example makes a broken copy fail. Switch examples on and watch which copies get caught.
Example tests (click to switch on)
| Broken copy of average | c1 | c2 | c3 | c4 | c5 | c6 | c7 | Verdict |
|---|---|---|---|---|---|---|---|---|
| divides by count - 1 | FAIL | would fail | would fail | would fail | would fail | pass | would fail | caught |
| returns 0 for an empty list instead of None | pass | pass | pass | pass | pass | would fail | pass | survives: nobody noticed |
| rounds the mean down (uses // instead of /) | pass | pass | would fail | would fail | pass | pass | pass | survives: nobody noticed |
| ignores the last value | FAIL | would fail | would fail | would fail | would fail | pass | would fail | caught |
| takes the absolute value of every number | pass | pass | pass | pass | would fail | pass | pass | survives: nobody noticed |
| rounds the mean to one decimal place | pass | pass | pass | would fail | pass | pass | pass | survives: nobody noticed |
| mishandles a list with exactly one value | pass | would fail | pass | pass | pass | pass | pass | survives: nobody noticed |
5 broken copies still pass your tests. Notice that c7 adds nothing once c1 is on: a test earns its place by catching something the others do not.
Which cases matter
- Typical: a normal input with an answer you can verify by hand.
- Boundaries: empty, one element, zero, the largest allowed value.
- Different kinds of input: negatives, repeated values, results that are not whole numbers.
- Failure: what the function does when the input is not acceptable. State it, then test it.
Predict first. A function divides a total by a count. Which extra case is most important to test?
Chapter context
Engineering makes a program understandable when it fails or changes. Separate pure calculations from external I/O so behavior can be checked cheaply and errors can be connected to specific inputs.
TRY IT YOURSELF
Exercise: write tests that catch seven broken copies
average(values)is correct below. A hidden checker also keeps **seven broken copies** of it (each has one realistic bug) and runs yourtest_averageagainst every one.- Write
assertlines insidetest_average(average). Use the **parameter**average, not the one defined above it, so the checker can swap in the broken copies. - A broken copy is caught when one of your asserts fails (or crashes) on it. The correct function must pass all of your asserts, so every expected value you write has to be right.
- Each result line names the assert that stopped the broken copy, so you can see which example earns its place.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
What extra case matters for a function dividing by a count?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
For money examples, use integer minor units rather than binary floating point. The subtotal is sum(quantity × unit_price), and the domain must specify rounding and tax rules before those operations are implemented.
Worked example
Two items cost 125 minor units each and three cost 40 each. The subtotal is 2×125 + 3×40 = 370. A test can exercise this calculation without starting an HTTP server. Request parsing and response formatting need separate checks.
Reference code
The following example uses the Python standard library and does not call a remote service.
def subtotal(items):
return sum(quantity * price for quantity, price in items)
assert subtotal([(2, 125), (3, 40)]) == 370
assert subtotal([]) == 0
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
Test coverage counts executed lines, not verified requirements. A container does not turn an unsafe operation into a safe one. JSON validity alone does not establish application validity.