You can write code that runs without errors and still be completely wrong. The
If the condition after
assert statement is a lightweight way to check assumptions during development.If the condition after
assert evaluates to True, execution continues silently. If it evaluates to False, Python immediately raises an AssertionError. This makes assertions perfect for catching logic mistakes before they become bugs.Basic syntax
# Passes: 5 is indeed greater than 3
assert 5 > 3, "Five should be bigger"
# Fails: raises AssertionError with the message below
# assert 2 + 2 == 5, "Math broke"The optional second argument is an error message. It only appears if the assertion fails.
In production code, Python can optimise away assertions using
In production code, Python can optimise away assertions using
python -O. So don't use them for user-input validation (use exceptions instead). Use them to verify that your internal logic holds true.Writing test functions
def add(a: int, b: int) -> int:
return a + b
def test_add() -> None:
assert add(1, 2) == 3, "Basic addition failed"
assert add(-5, -5) == -10, "Negative numbers failed"
test_add()Notice how
This pattern scales well: one test function per feature, each checking several cases.
test_add calls the target function multiple times with different inputs. Each call has its own assertion and message.This pattern scales well: one test function per feature, each checking several cases.
Edge cases matter most
def first_char(s: str) -> str:
if not s:
return ""
return s[0]
def test_first_char() -> None:
assert first_char("hello") == "h", "Normal string failed"
assert first_char("") == "", "Empty string must return empty"
assert first_char("x") == "x", "Single char failed"Edge cases are inputs at the boundary of what is valid: empty strings, zero, negative numbers, single-element lists. They fail more often than typical values because they exercise special branches in your code.
Writing tests before or while you write a function forces you to think about these boundaries explicitly. It changes how you design functions — you start asking "what should happen when the list is empty?" before writing
Writing tests before or while you write a function forces you to think about these boundaries explicitly. It changes how you design functions — you start asking "what should happen when the list is empty?" before writing
for loops.