Written and maintained by CASRAI Editorial Board
Last updated
A pytest fixture is a function, marked with @pytest.fixture, that supplies a test with the data or resources it needs; a test uses it by naming it as an argument. This guide is for researchers who already have a first test running and now need shared setup (input data, temporary files, connections) and reliable cleanup. It follows the current pytest documentation, read on 2026-11-07.
What fixtures are used for in research code
The pytest documentation states that test functions request the fixtures they require by declaring them as arguments. In research code this replaces copy-pasted setup at the top of every test: the small input table, the output directory, the opened connection. Each test states what it needs in its signature, and pytest builds it. For the basics of writing a first test and the vocabulary around test fixtures, see What Is Unit Testing? Examples in Python and R; for measuring how much of your code the tests exercise, see Codecov for research code. Tests of this kind support computational reproducibility because they make the conditions of each check explicit.
Install or access
Install pytest following the official documentation, ideally inside a virtual environment:
python3 -m venv .venv
source .venv/bin/activate # on Windows: .venvScriptsactivate
pip install -U pytest
pytest --version
The same page also shows uv add --dev pytest for projects managed with uv (prefix later commands with uv run). Running pytest with no arguments collects files matching test_*.py or *_test.py in the current directory and its subdirectories.
Step by step: define, request, share and tear down a fixture
1. Define a fixture and request it
Decorate a function with @pytest.fixture, then list its name as a parameter of any test that needs it. The numbers below are illustrative toy data.
import pytest
@pytest.fixture
def measurements():
return [2.0, 4.0, 6.0]
def test_mean(measurements):
assert sum(measurements) / len(measurements) == 4.0
2. Use the built-in tmp_path fixture for files
pytest ships a tmp_path fixture: a temporary directory unique to each test function, provided as a pathlib.Path. It suits analysis code that writes output files, because nothing is written into your project folder.
CONTENT = "content"
def test_create_file(tmp_path):
d = tmp_path / "sub"
d.mkdir()
p = d / "hello.txt"
p.write_text(CONTENT, encoding="utf-8")
assert p.read_text(encoding="utf-8") == CONTENT
3. Clean up with yield
Use yield instead of return; code after the yield runs as teardown once the test finishes. When several fixtures are active, teardown runs in the reverse order of setup.
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
conn = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
yield conn
conn.close()
The pattern is the same for any resource you open and must close, such as a database handle or an instrument connection.
4. Choose a scope
By default a fixture is created for each test function. Set scope to reuse an expensive setup across more tests, as in @pytest.fixture(scope="module"). The documented values are:
| Scope value | Meaning |
|---|---|
function |
Default; one fixture instance per test function |
class |
Shared within a test class |
module |
Shared within a test module (file) |
package |
Shared within a package |
session |
Shared across the whole test session |
5. Share fixtures with conftest.py
Fixtures defined in a conftest.py file can be used by tests in that directory without any import. Put project-wide fixtures there rather than copying them between test files.
6. Autouse and parametrized fixtures
With autouse=True, every test automatically requests the fixture, which suits setup that all tests need. With params, as in @pytest.fixture(scope="module", params=[...]), the current value is available as request.param and every dependent test runs once per value. That is a compact way to run the same check against several input values.
Output and how to read it
To see which fixtures are available, run pytest --fixtures. It lists the built-in fixtures and your own; fixtures whose names start with an underscore are hidden unless you add -v.
For tmp_path, pytest keeps the last 3 temporary directories by default (controlled by the tmp_path_retention_count and tmp_path_retention_policy settings), laid out as {temproot}/pytest-of-{user}/pytest-{num}/{testname}/. This lets you inspect files from a recent failing run. If you pass --basetemp=mydir, that directory is cleared blindly before each run, so never point it at a folder holding data you want to keep.
Free alternatives
pytest is itself free, open-source software. These other free Python test tools cover similar ground, but they do not use pytest’s fixture-by-argument model.
| Tool | What it is | Setup and teardown approach | Official docs |
|---|---|---|---|
| unittest | Python’s built-in unit testing framework, part of the standard library | setUp()/tearDown() run around each test method; setUpClass()/tearDownClass() run once per class; addCleanup() registers cleanup functions that run in reverse order |
unittest documentation |
| nose2 | A test runner that extends unittest | Builds on unittest’s fixtures and adds features such as the params decorator for running one test function with several values |
nose2 documentation |
unittest needs no installation, which can matter on locked-down institutional machines.
Troubleshooting
- A test cannot find a fixture. Run
pytest --fixturesto confirm pytest sees it; shared fixtures belong in aconftest.pyin the tests’ directory. - Cleanup code never runs. Teardown belongs after a
yield; code placed after areturnis never reached. - Expensive setup repeats for every test. The default scope is
function; widen it withscope="module"orscope="session", remembering that tests then share that fixture instance. - Files from an earlier run are missing. Only the last 3
tmp_pathdirectories are kept by default; adjust the retention settings if you need more.
FAQ
What is a pytest fixture?
A function decorated with @pytest.fixture that provides setup (and optionally teardown) to tests, which request it by listing its name as an argument.
How do I run teardown code after a test?
Write the fixture with yield instead of return and put the cleanup after the yield.
Where do shared fixtures go?
In conftest.py; tests in that directory can use them without importing.
How do I run one test with several input values?
Give the fixture a params list and read request.param; dependent tests run once per value.
Sources
- pytest documentation, How to use fixtures (read 2026-11-07)
- pytest documentation, How to use temporary directories and files in tests (read 2026-11-07)
- pytest documentation, Get started (read 2026-11-08)
- Python documentation, unittest (read 2026-11-08)
- nose2 documentation, nose2 (read 2026-11-08)







