pytest Tutorial
This page shows how pytest runs plain functions with the assert statement and reports the values on failure. It also covers fixtures, parametrize, built-in helpers, flags, a unittest comparison, and common mistakes.
Run a plain function with assert
pytest finds files named test_*.py and functions named test_*. You do not need a class. The assert statement checks a condition. When the condition is false, pytest shows the values of both sides.
Install pytest with pip. Then write a small test file. Run the command pytest in the same folder. The output shows the file, the line, and the values that failed.
pip install pytest
# test_math.py
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
def test_add_fail():
assert add(2, 3) == 6
Fixtures and conftest.py
A fixture is a function with the @pytest.fixture decorator. A test gets the fixture value by naming it as an argument. Use yield to give the value and to run teardown code after the test.
Put shared fixtures in a file named conftest.py. pytest loads this file automatically. Tests in the same folder can use the fixtures without an import.
The example below creates a list fixture. The test adds one item. After the test, the code after yield clears the list.
import pytest
@pytest.fixture
def items():
data = [1, 2]
yield data
data.clear()
def test_add_item(items):
items.append(3)
assert items == [1, 2, 3]
Parametrize one test with many inputs
The @pytest.mark.parametrize decorator runs one test function many times. Each tuple in the list is one run. pytest reports each run as a separate test.
The example checks a square function with three inputs. pytest runs three tests and shows one result for each tuple. A failure names the exact input.
import pytest
def square(x):
return x * x
@pytest.mark.parametrize("value,expected", [(2, 4), (3, 9), (0, 0)])
def test_square(value, expected):
assert square(value) == expected
Built-in helpers and flags
The tmp_path fixture gives a fresh temporary folder for each test. The capsys fixture captures printed output. Use capsys.readouterr() to read the text.
The -k flag selects tests by name. The -x flag stops the run at the first failure. These flags help you focus on one problem at a time.
def test_print(capsys):
print("hello")
out = capsys.readouterr().out
assert out == "hello\n"
def test_file(tmp_path):
path = tmp_path / "data.txt"
path.write_text("ok")
assert path.read_text() == "ok"
Compare with unittest
unittest needs a class that inherits from TestCase. Each check uses a method such as assertEqual. pytest needs only a function and the assert statement, so it has less boilerplate.
pytest shows the values of a failed assert. unittest shows a message but often not the values. This makes a pytest failure faster to read.
unittest uses setUp and tearDown methods. pytest uses fixtures, and a fixture can return a value. A fixture can also run only when a test needs it.
Mistakes to avoid
Mistake one: a fixture hides a slow network call. The test looks fast but it waits on the network. Fix: use a local fake or a stub in the fixture. Keep the network call in a separate integration test.
Mistake two: tests pass alone and fail together. Shared state causes this problem. Fix: give each test its own data, and clean up in the fixture teardown. Run the full suite to find the order problem.
Common questions
Do I need to install pytest?
Yes, if pytest is not already in your environment. Run pip install pytest to add it.
What is conftest.py?
It is a file that pytest loads automatically for shared fixtures and hooks. Tests in the same folder can use its fixtures without an import.
How do I run a single pytest test?
Use the -k flag with a name, for example pytest -k test_add. You can also give the file and the test node, for example pytest test_math.py::test_add.