Q&A library Python

How do I write tests with pytest in Python?

Python Advanced fixturespytesttesting

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.

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project