Reference · module 17
Checking your own code
Everything this module explains, on one page.
assert
State what must be true and let Python complain when it is not.
- assert takes something that should be True and does nothing if it is. If it is False, it stops the program with an AssertionError. That is the whole of it: one word, one condition.
- So silence is the good outcome. A file full of asserts that prints nothing at all is a file where everything you claimed turned out to hold.
- A comma adds a message: assert total > 0, "total came out empty". You write it for the person reading the failure, and that person is usually you, later, in a hurry.
The mistake you are about to make
assert(total > 0, "empty")
assert total > 0, "empty"
The brackets look like a function call and it is not one. Inside them you get a two-item tuple, and a non-empty tuple is always true: the check passes every time, silently.
The edges
Try your function on the inputs that break it, not the ones you had in mind.
- Code is written with one input in mind and breaks on all the others. The three that break it most often are the empty one, the one with a single item, and the one of the wrong type.
- The empty one is the classic. An average over an empty list divides by zero; a max over one raises; a loop over one quietly does nothing and returns whatever the starting value was.
- So decide out loud what the empty case should do, and write that down as a check. Something has to be right: zero, None, or a raise. Undecided is the one answer that is not.
The mistake you are about to make
sum(xs) / len(xs)
0 if not xs else sum(xs) / len(xs)
On your data the list is never empty, so the line works and looks finished. The first empty day divides by zero — and it fails not here, but wherever the average was wanted.
A check you can rerun
Turn a one-off check into something that still runs next month.
- A test is an ordinary function with no arguments, holding a few asserts. Give it a name that says what it claims — test_empty_list_gives_zero — and the name alone explains the failure.
- Inside, three steps in order: build the input, call the thing, assert the result. Every test in the world has this shape, and keeping to it makes an unfamiliar test readable in one pass.
- Then call them at the bottom of the file, under the main guard, and running the file runs the checks. Real projects use pytest, which finds them by the test_ prefix and does exactly this.
The mistake you are about to make
test_empty() — defined and never called
at the bottom of the file: test_empty()
The file of tests runs, prints nothing, and that looks like everything passed. Here the silence means nothing ran at all: defining a test is not running it.
What to check
Write the few checks that pay, and skip the many that do not.
- Check what the function promises, not how it keeps the promise. average([2, 4]) == 3 is a promise. Whether it used sum or a loop is nobody's business, and a check that knows will break on the first tidy-up.
- You do not need a check per line. Two or three per function is usually the whole payoff: the ordinary case, the empty one, and whatever surprised you while writing it.
- And the best check is the one written the moment something breaks. A bug you have just found is a bug that can come back, and a check is how you say it may not.
The mistake you are about to make
checking that sum is called inside
checking that average([2, 4]) is 3
Checking the innards feels thorough. That check fails on every harmless rewrite and fails on none of the real bugs — it works exactly backwards.
A print or a check
Know when a print is enough and when it is costing you the same work twice.
- A print asks a person to look at a number and decide whether it is right. That works once. Tomorrow, on twenty numbers, nobody looks — and the wrong one goes past unread.
- An assert does the deciding itself. That is the entire difference: the same knowledge, written down once, applied by the machine every time instead of by you.
- So a print is right while you are still working out what the answer should be, and a check is right the moment you know. Prints get deleted; checks stay in the file.
The mistake you are about to make
print(average([2, 4]))
assert average([2, 4]) == 3
You already know it should be three, and you print it anyway to have a look. The knowledge exists and lives only in your head, so tomorrow you check it again by hand.