Topic 01

A Test Is a Promise You Can Run

Hands-on

So far, checking a program has meant running it and looking at the output. That is slow, it depends on your memory of what the right answer was, and it quietly stops happening once a program gets long. A test writes down what a function should do and checks it automatically, so every future change can be checked in a second instead of by hand. The simplest test needs only one keyword: assert.

Think of the checklist taped inside every aircraft cockpit. Pilots know how to fly; they use the checklist anyway, every time, because the things that must be true before take-off are too important to leave to memory.

assert

assert is followed by a condition. If the condition is True, nothing happens at all and the program carries on. If it is False, Python stops with an AssertionError. So assert calculate_tip(40, 15) == 6 is a promise written as code: the tip on 40 at 15 percent is 6, and if that ever stops being true, stop and say so.

Testing by Examples

Good checks come in three kinds. Normal cases: a typical bill and a typical tip. Edge cases: the boundaries, such as 0 percent and 100 percent, where Chapter 3 showed that rules tend to break. And bad input that should be refused, such as a percentage of 250, which the next topic tests properly. The file below keeps the tip function in tips.py, as in Chapter 9, and checks it in a separate file.

tips.py
def calculate_tip(bill, percent):
    """Return the tip for a bill at the given percentage."""
    if not 0 <= percent <= 100:
        raise ValueError(f"percent must be between 0 and 100, got {percent}")
    return bill * percent / 100
check_tips.py
from tips import calculate_tip

assert calculate_tip(40, 15) == 6
assert calculate_tip(40, 0) == 0
assert calculate_tip(40, 100) == 40
print("All checks passed.")
Running python3 check_tips.py
All checks passed.

A Test That Catches a Mistake

The value of a test shows when something changes. Suppose someone edits calculate_tip and, on the return line, divides by 10 where it should divide by 100. Run the checks again and the first assert fails at once, pointing at the exact promise that was broken. Without the check, the tips would be ten times too large, and the first to notice might be a customer.

After the edit, running python3 check_tips.py again
Traceback (most recent call last):
  File "/Users/you/python-course/check_tips.py", line 3, in <module>
    assert calculate_tip(40, 15) == 6
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

A bare AssertionError says which promise broke but not what the function actually returned. The next topic's tool, pytest, fills in exactly that.

What a Test Cannot Do

A test checks the examples you wrote, and only those. Three passing checks prove that those three cases work, not that every possible input does. That is why the choice of examples matters, boundaries especially, and why tests give confidence rather than proof. Testing as a discipline, with its own methods for choosing cases, is covered in Software Development from Zero and taught in full in QA & Software Testing for Beginners.

Two ways to know a change did not break anything
Without tests
Change, run, look at the output, and hope you remember what it should be.
With tests
Change, run the tests, and know.
Common Confusions
  • "Tests prove the code is correct." They prove it works for the cases tested. Choose those cases carefully, boundaries first.
  • "Tests are for big teams." The first person a test saves is the one who wrote it, usually a week later.
  • "A passing assert prints a message." It prints nothing at all. Silence is the success; only a failure speaks.
Why It Matters
  • Tests turn "I think it still works" into "it still works", in seconds, every time.
  • Functions written as parameters in, return out, as Chapter 5 recommended, are exactly the ones that are easy to test.

Knowledge Check

What does assert calculate_tip(40, 15) == 6 do when the tip is 6?

  • Nothing: a true assert is silent
  • It prints True in the terminal
  • It returns 6 to the line that called it
  • It stops the program to show the check passed

Which set of examples tests a 0-to-100 percentage rule best?

  • 15, 20 and 25, three typical tip percentages
  • A normal value, 0, 100, and something outside the range
  • Every whole number from 1 all the way up to 99
  • One very large value, chosen to stress the function hard

Three asserts pass. What has been shown?

  • The function is correct for every possible input
  • The function will never need to be changed
  • The program now runs faster than it did before the tests
  • Those three cases work as the tests describe

Why is a bare AssertionError less helpful than it could be?

  • It does not say which line of the file failed
  • It does not show what value the function actually returned
  • It hides the name of the function that was being tested there
  • It stops the program even when the check passed

You got correct