Lesson 24 · Intermediate · 18 min

Python Testing with assert

Turn expected behavior into checks you can run again.

asserttestingAssertionErroredge cases

Maya edits the total formula and needs to know the 18.75 order still adds up. One assert answers that in a second. When is an assert the wrong tool?

An assert statement checks a condition and raises AssertionError when it is false. It is a compact way to document and verify expectations while learning. Use it for internal checks and tests, not for validating untrusted user input. You will also table-drive cases, assert an expected exception, and compare floats with a tolerance.

Assert a fact

assert add(2, 3) == 5 states the expected result in executable form. If the function changes and returns 6, the program stops at that line. A short message after a comma explains what failed.

def add(a, b):
    return a + b
assert add(2, 3) == 5
print("ok")

A passing check

Test several cases

A small set of asserts can cover normal input and an edge case. Put the most important behavior first. When one fails, Python reports the line and stops the current test run, so later checks are not reached.

def slugify(text):
    return text.strip().lower().replace(" ", "-")
assert slugify(" Hello World ") == "hello-world"
assert slugify("Python") == "python"
print("2 tests passed")

More than one example

Catch a failure to inspect it

AssertionError is an exception like any other. Catching it in a tiny example lets you print the message without a traceback. In a real test suite, let the runner report the failure instead of hiding it.

def double(n):
    return n * 2
try:
    assert double(3) == 7, "double(3) should be 6"
except AssertionError as error:
    print(error)

Read the failure message

Put cases in a table

A list of (input, expected) pairs makes the covered cases visible and keeps each assertion short. Include a message with repr() so a failure shows the exact value. The first failed assert stops the run, which is why a test runner reports one failure at a time.

def slugify(text):
    return text.strip().lower().replace(" ", "-")

cases = [(" Hello World ", "hello-world"), ("Python", "python"), ("", "")]
for raw, expected in cases:
    actual = slugify(raw)
    assert actual == expected, f"{raw!r}: {actual!r} != {expected!r}"
print(len(cases), "cases passed")

One table, one clear failure message

Assert the exception, not silence

Some behavior is an error by design. Call the function, catch only the expected exception, and assert that it happened; an else branch catches the case where no error was raised. In a real suite, use pytest.raises or unittest's assertRaises instead of a bare try.

def parse_age(raw):
    value = int(raw)
    if value < 0:
        raise ValueError("age must be non-negative")
    return value

for raw in ["-1", "many"]:
    try:
        parse_age(raw)
    except ValueError as error:
        print(raw, type(error).__name__, error)
    else:
        print("no error", raw)

The expected failure is part of the contract

Compare floats with a tolerance

Binary floating point makes 0.1 + 0.2 slightly different from 0.3. A test that demands exact equality is testing the representation, not the intended value. Use math.isclose with a documented tolerance, or compare integer cents when the domain is money.

import math
total = 0.1 + 0.2
print(total == 0.3)
print(math.isclose(total, 0.3, rel_tol=1e-9))
assert math.isclose(total, 0.3, rel_tol=1e-9)
print("close enough")

Test the value within a stated tolerance

Key points

  • assert condition raises AssertionError when the condition is false.
  • A comma adds a message that explains the expectation.
  • Asserts are for tests and internal invariants, not user validation.
  • Python's -O flag removes assert statements, so never rely on them for security.
  • Tests need visible cases, explicit expected errors, and tolerance for floats.

Common mistakes

  • Using assert to validate a form or password.
  • Testing only the happy path and skipping empty or boundary input.
  • Writing a vague message that does not say what was expected.
  • Using assert for user validation; -O removes it from the running program.
Your turn

Write square(n), assert square(4) == 16 and square(0) == 0, then print 'ok'.

Try it yourself

Change the values, predict the output, then run the code. The Python runtime is downloaded the first time you press Run, so the first run can take a few seconds.

In-browser PythonRuns locally · no account
First run downloads Pyodide from a CDN. Use Run code after editing.

Practice this lesson

12 questions with immediate explanations.

See all 12 answersStart practice
← dates & timestype hints →