← back to section

Code that computes and prints is not a test yet. It becomes one when it decides for itself whether things add up and says so loudly when they do not. One word does that: assert.

assert: a claim that must hold

live example

code = 200            # the response code from the service
assert code == 200
print("the check passed — things add up")
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

It reads as "I claim: code equals 200". If that is true, the line does nothing and execution continues. If it is false, the program stops with AssertionError and the test counts as failed.

People often print the result and look at it instead:

live example

# the wrong way: printing stops nothing
code = 404
if code != 200:
    print("did not match")
print("reached the end — so it 'passed'")
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

print breaks nothing: such a check stays green whatever happens. assert stops and marks the failure — that is what separates a test from a script.

Practice here: A check with assert

The message is half the value

A bare assert says only AssertionError on failure — what did not match, you find out separately. So a message goes after the condition, separated by a comma:

live example

# error: this block fails on purpose — the message shows the actual value
code = 404
assert code == 200, f"expected 200, got {code}"
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

AssertionError: expected 200, got 404

The rule is simple: the message must contain the actual value. "Did not match" does not help; "expected 200, got 404" ends the question at once. For a large response, add the order number the response can be found by.

One check, one claim

live example

# the wrong way: two conditions in one assert
response = {"status": "PAID", "total": 4990}
assert response["status"] == "PAID" and response["total"] == 4990
print("matched — but which of the two would have failed is unknown")
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

live example

# the right way: every field gets its own claim and its own message
response = {"status": "PAID", "total": 4990}
assert response["status"] == "PAID", f"status: {response['status']}"
assert response["total"] == 4990, f"total: {response['total']}"
print("both fields matched")
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

In the second case the failure names the field right away. In the first you only learn that "one of the two" did not match.

Practice here: Collect checks into a set

Errors that are not about the check

Besides AssertionError there are errors in the code itself — a different thing. A check failing with KeyError: 'total' means you asked for a field that is not there. That may be a defect (the field must exist) or a bad check (the field is optional). Telling them apart is your job.

The most common ones:

ErrorWhat happened
KeyError: 'total'no such key in the dict
IndexErrorno element with that index
TypeErroradded a string to a number
NameErrorused a name that does not exist (typo)

Read the message bottom up: the last line says what happened, the lines above say where.

Practice here: Different errors, different messages

try / except: catch an error and stay alive

Sometimes a failure is expected: the data holds a string that does not turn into a number, and the work has to go on. try means "try to do this", except means "if this kind of error happened, do that instead". Catch a specific kind of error, not everything.

live example

# the wrong way: except Exception hides the typo as well
try:
    total = int("4990")
    print(tota)
except Exception:
    pass
print("green, although nobody printed the total")
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

live example

# the right way: catch only the error you expected
text = "not a number"
try:
    total = int(text)
except ValueError:
    total = 0
print(total)
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

In the first version except Exception swallowed the typo in tota — the code ran to the end as if nothing had happened. pass inside except is almost always a sign that nobody wanted to think about the error.

try/except is needed less often than it seems: a check is supposed to fail — that is its job. You catch an error where you check that it happened.

Practice here: Catch an error

Checking that an error did happen

live example

try:
    int("not a number")
    assert False, "expected ValueError, but it did not happen"
except ValueError:
    print("ValueError happened — as it should have")
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

The break here is the absence of the error: if int ever swallows "not a number", assert False fires.

In short

  • assert condition, "message" is what turns code into a test: on falsehood it stops and marks the failure, while print would leave the check green.
  • The message must carry the actual value: "expected 200, got 404".
  • One check, one claim — otherwise you cannot tell what broke.
  • KeyError, IndexError, TypeError are code errors, not check failures; read the message bottom up.
  • try/except catches a specific error; except Exception: pass leaves a green result on broken code.