pytest Setup
pytest discovers and runs tests by convention - no boilerplate test classes required. A tests/ directory, a pyproject.toml config block, and uv add --group dev pytest is enough to start.
Recipe
uv add --group dev pytest
mkdir tests[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]uv run pytestWhen to reach for this:
- Starting tests on a new Python project
- Migrating from
unittestto pytest - Configuring test discovery for
src/layout
Working Example
myapp/
├── pyproject.toml
├── src/myapp/core.py
└── tests/
├── conftest.py
└── test_core.py
# tests/test_core.py
from myapp.core import add
def test_add():
assert add(2, 3) == 5[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
addopts = "-ra -q"
filterwarnings = ["error"]uv run pytest -v
uv run pytest tests/test_core.py::test_addWhat this demonstrates:
- Tests live in
tests/, namedtest_*.py pythonpath = ["src"]enables imports without editable install in simple setupsconftest.pyholds shared fixturesaddoptssets default CLI flags for every run
Deep Dive
Discovery Rules
- Files:
test_*.pyor*_test.py - Functions:
test_* - Classes:
Test*(no__init__) - Plugins extend discovery (e.g.,
pytest-asynciofor async tests)
Config Locations
| File | Priority |
|---|---|
pyproject.toml [tool.pytest.ini_options] | Preferred |
pytest.ini | Legacy |
conftest.py hooks | Per-directory |
Gotchas
- Tests in project root - collected accidentally. Fix:
testpaths = ["tests"]limits discovery. - Import errors without src on path -
ModuleNotFoundError. Fix:pythonpath = ["src"]orpip install -e .. - Running pytest without dev deps -
pytest: command not found. Fix:uv sync --group devoruv run pytest. - Duplicate
conftest.pyfixtures - name collisions across directories. Fix: scope fixtures carefully; use unique names. - Warnings as noise - real issues hidden. Fix:
filterwarnings = ["error"]to fail on unexpected warnings.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| unittest | Stdlib only, no deps | You want fixtures and plugins |
| nose2 | Legacy nose migration | New projects |
| behave | BDD acceptance tests | Unit/integration tests |
FAQs
Where do tests go?
tests/ at project root is the convention. Mirror src/ structure inside it.
Do I need __init__.py in tests/?
No for pytest 7+. Empty tests/ without __init__.py works fine.
How do I run one test?
pytest tests/test_core.py::test_add or pytest -k "add".
What is conftest.py?
Shared fixtures and hooks. Loaded automatically from test directory up to root.
How do I skip a test?
@pytest.mark.skip(reason="...") or @pytest.mark.skipif(condition, reason="...").
Can pytest run unittest tests?
Yes. pytest collects unittest.TestCase subclasses automatically.
How do I see print output?
pytest -s disables output capture.
What is addopts?
Default CLI arguments applied to every pytest invocation.
How do I configure markers?
markers = ["slow: marks slow tests"] in config; register all custom markers.
pytest or unittest?
pytest for new projects. Richer fixtures, plugins, and less boilerplate.
Related
- Assertions & Test Structure - writing good tests
- Fixtures - setup and teardown
- Coverage & Reporting - measuring test reach
- Testing Best Practices - team conventions
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.