Legends ofPythos
Claim your name
Pythonic Python

Checking your own work

Lesson 10 of 12

Watch the lesson1:04 · with Sigrid
You can write code that runs without errors and still be completely wrong. The 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"
Assertions stop execution when they fail
The optional second argument is an error message. It only appears if the assertion fails.

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()
A function whose only job is to verify behaviour
Notice how 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"
Testing the boundaries of your input
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 for loops.

Your turn

0 of 2 solved

Exercise 1

+40 XP
Complete test_sum() so it verifies that sum_positive returns correct results for: a normal list, an empty list, and a list with only negative numbers. Use at least one assertion per case.
def sum_positive(nums: list[int]) -> int:
    return sum(n for n in nums if n > 0)


def test_sum() -> None:
    pass

Run your code to check it against the tests.

Exercise 2

+40 XP
Write a function clamp(value: float, lo: float, hi: float) -> float that returns the value constrained between lo and hi. Then write test_clamp() with assertions for at least three cases including an edge case where value == lo, one where value > hi, and one normal in-range value.
def clamp(value: float, lo: float, hi: float) -> float:
    pass


def test_clamp() -> None:
    pass




test_clamp()

Run your code to check it against the tests.