Why testing matters, pytest basics, and unit tests

The assert statements you've already been using

Every hidden test case in this entire course, all the way back to Lesson 1, has been a plain assert statement checking a function’s behavior against known inputs and expected outputs. That’s not a simplification for teaching purposes — it’s genuinely what a test is at its core. What’s been missing is a framework around those assertions: a way to discover, run, and report on many of them at once, consistently, instead of manually running a script and eyeballing whether it crashed.

pytest is that framework — the standard tool for writing and running tests in Python.

Test discovery and a first test

pytest finds tests by convention, not configuration: a file named test_*.py (or *_test.py), containing functions named test_*, is automatically discovered and run — no registration step, no import list to maintain.

# validators.py
def is_valid_tool_name(name) -> bool:
    return isinstance(name, str) and len(name) > 0
# test_validators.py
from validators import is_valid_tool_name

def test_valid_name_returns_true():
    assert is_valid_tool_name("search") == True

def test_empty_string_returns_false():
    assert is_valid_tool_name("") == False

def test_non_string_returns_false():
    assert is_valid_tool_name(42) == False

Running pytest from the command line finds and runs every test_* function in every test_*.py file it can locate:

pytest
click Run to see this pane's output

Each . represents one passing test. This is exactly the same is_valid_tool_name function from the OOP lesson’s decorators concept — pytest isn’t asking you to write tests differently than the assert-based checks you’ve already been reading all course; it’s giving you a real tool to run them.

What a failing test actually shows you

def test_broken_example():
    assert is_valid_tool_name("search") == False   # deliberately wrong
pytest, with a failing test
click Run to see this pane's output

pytest shows exactly which assertion failed, what the actual value was versus what was expected, and the specific line — considerably more information than a bare AssertionError with no context would give you on its own.

What makes this specifically a unit test

test_valid_name_returns_true and its neighbors are unit tests — each one tests a single, small unit of code (here, one function) in complete isolation: no file I/O, no network call, no database, nothing external to the function itself. This matters because it’s what makes unit tests fast (thousands can run in seconds) and deterministic (the same input always produces the same result, with nothing external able to make a run flaky). Not every test in this lesson will look like this — testing a full FastAPI endpoint, covered next, involves considerably more moving parts working together, which is a meaningfully different kind of test, not just a bigger unit test.

Check your understanding
1/5

What convention does pytest use to discover which functions are tests?