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 brokeRed → 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.