Chapter 4 · Mid-level Developer

Writing tests: assert and pytest-style

Tests are code that checks your code. They turn "I think this works" into "I can prove this works" — and they catch the moment a future change quietly breaks something.

The building block is assert: assert condition does nothing if the condition is true, and raises AssertionError if it's false. Add a message for context: assert total == 200, f'got {total}'. A test is just a function that calls your code and asserts the expected result.

The industry-standard runner is pytest, and its convention is delightfully simple: write functions named **test_***, put your asserts inside, and pytest finds and runs them, reporting each as pass or fail with a readable diff.

The rhythm is red → green: write a test for the behaviour you want (it fails — *red* — because the code is wrong or missing), then fix the code until the test passes (*green*). A failing test first is a feature, not a problem — it proves the test can actually catch the bug.

Syntax

# A bare assertion:
assert calculate_tax(1000, 20) == 200.0
assert value == expected, f'got {value}'   # with a message

# A pytest-style test — just a test_* function full of asserts:
def test_basic_tax():
    assert calculate_tax(1000, 20) == 200.0

def test_zero_income():
    assert calculate_tax(0, 20) == 0.0

# Run with: pytest   (it auto-discovers test_* functions)

Worked examples

assert passes silently, fails loudly

assert 2 + 2 == 4          # nothing happens — it's true

assert 2 + 2 == 5, 'maths broke'
# Output (raised):
# AssertionError: maths broke

Red → green: tests catch the bug, then confirm the fix

def calculate_tax(income, rate):
    return round(income * rate / 100, 2)   # fixed formula

def test_basic_tax():
    assert calculate_tax(1000, 20) == 200.0

def test_zero_income():
    assert calculate_tax(0, 20) == 0.0

def test_high_rate():
    assert calculate_tax(2000, 45) == 900.0

test_basic_tax()
test_zero_income()
test_high_rate()
print('All tests passed!')
# Output:
# All tests passed!

A failing test points straight at the problem

def calculate_tax(income, rate):
    return round(income / rate * 100, 2)   # BUG: / instead of *

def test_basic_tax():
    assert calculate_tax(1000, 20) == 200.0

test_basic_tax()
# Output (raised):
# AssertionError
# (calculate_tax(1000, 20) returned 5000.0, not 200.0)

Common mistakes & gotchas

Testing the wrong thing (or nothing)

assert calculate_tax(1000, 20) with no comparison just checks the result is *truthy* — any non-zero number passes, bug or not. Always assert against a specific expected value: == 200.0.

Float comparison can surprise you

0.1 + 0.2 == 0.3 is False due to floating-point rounding. When comparing computed floats, compare to a rounded value or use math.isclose(a, b) / pytest's approx, rather than exact ==.

asserts can be stripped out

Running Python with the -O (optimise) flag *removes all assert statements*. Great for tests, but never use assert for production validation (e.g. checking user input) — use a real if ... raise there, and reserve assert for tests.

Why it matters

Tests are what let you change code without fear. A suite of green tests means you can refactor, upgrade, and add features knowing you'll be told the instant something breaks — and they're your final promotion piece here, because professional code does not ship untested. This is also the gateway to AI-assisted coding: tests are how you verify that generated code actually does what it claims.