Foundations: Python, Git and toolsLoading progress…

Tests as examples

Build a small HTTP service with clear boundaries, tests, logs and a repeatable runtime.

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 averagec1c2c3c4c5c6c7Verdict
divides by count - 1FAILwould failwould failwould failwould failpasswould failcaught
returns 0 for an empty list instead of Nonepasspasspasspasspasswould failpasssurvives: nobody noticed
rounds the mean down (uses // instead of /)passpasswould failwould failpasspasspasssurvives: nobody noticed
ignores the last valueFAILwould failwould failwould failwould failpasswould failcaught
takes the absolute value of every numberpasspasspasspasswould failpasspasssurvives: nobody noticed
rounds the mean to one decimal placepasspasspasswould failpasspasspasssurvives: nobody noticed
mishandles a list with exactly one valuepasswould failpasspasspasspasspasssurvives: 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.

Start with c1 alone: it catches only some bugs. Add the boundary cases (empty list, single value) and the awkward ones (a mean that is not whole, a negative number).

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.

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.