How do I write tests with pytest in Python?
Short answer
Write functions named test_ that use plain assert, and run pytest to discover and run them. Use fixtures for shared setup and parametrize to run one test over many inputs.
The short version
Write test functions whose names start with test_, put them in files named test_*.py, and use plain assert. Running pytest discovers and runs them, reporting failures with helpful detail. Use fixtures to share setup between tests, and parametrize to run the same test across many inputs.
A first test
# test_maths.py
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
Run it with pytest in the terminal. Unlike some frameworks, pytest uses the ordinary assert statement and rewrites it to show the actual values when it fails, so you see exactly what was expected and what was received.
Running one input many ways with parametrize
Rather than copying a test for each case, parametrize feeds a list of inputs to one test.
import pytest
@pytest.mark.parametrize("a, b, expected", [
(1, 1, 2),
(2, 3, 5),
(0, 0, 0),
])
def test_add(a, b, expected):
assert add(a, b) == expected
pytest runs this as three separate tests, so a failure tells you exactly which input broke, which a single test with a loop cannot.
Sharing setup with fixtures
A fixture is a function that provides a value to any test that asks for it by name. It replaces repetitive setup code.
@pytest.fixture
def sample_orders():
return [{"item": "pen", "qty": 2}, {"item": "mug", "qty": 1}]
def test_total_quantity(sample_orders):
assert sum(o["qty"] for o in sample_orders) == 3
The test declares sample_orders as a parameter, and pytest runs the fixture and passes in its return value. Fixtures can also clean up after a test by yielding the value and running teardown code after the yield.
Testing that code raises
To check that a function raises the right error, use pytest.raises as a context manager.
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
1 / 0
The test passes only if the expected exception is raised inside the block, which is the clean way to test error handling.
Why pytest over unittest
The standard library's unittest works, but pytest needs far less boilerplate: no test classes, no self.assertEqual, just functions and assert. Its fixtures and parametrisation are more flexible, and its failure output is clearer. For most projects pytest is the practical default, and it can even run existing unittest tests.
Structuring and running
Keep tests in a tests folder mirroring your code. Run the whole suite with pytest, a single file with pytest tests/test_maths.py, and a single test with pytest tests/test_maths.py::test_add. Add -v for a verbose list and -k "add" to run tests whose names match a substring, which is useful while focusing on one area.