pytest Testing Configuration Reference
pytest testing configuration reference
TL;DR
- Bottom line: Configure pytest in
pyproject.tomlunder[tool.pytest.ini_options]; useconftest.pyfor shared fixtures; register custom markers; usesrclayout withpythonpath = ["src"]. - Key tool/command:
pytest -v --tb=short - Watch out for: Misspelling
parametrizeasparameterize— pytest silently ignores the decorator. - Works with: pytest 7.x–8.x, Python 3.8+, all major plugins.
Constraints
- pytest requires Python 3.8+ (pytest 8.x)
- Custom markers must be registered — unregistered markers trigger warnings
conftest.pyfiles are auto-discovered — never import them directly- Fixture scope hierarchy: session > package > module > class > function
parametrizeis spelled without the second 'e'
Quick Reference
| Setting | Location | Purpose |
|---|---|---|
[tool.pytest.ini_options] | pyproject.toml | Main config section |
testpaths | pyproject.toml | Test directories |
pythonpath | pyproject.toml | sys.path additions |
addopts | pyproject.toml | Default CLI options |
markers | pyproject.toml | Register custom markers |
conftest.py | test directory | Shared fixtures |
@pytest.fixture | conftest/test | Reusable test setup |
@pytest.mark.parametrize | test function | Multiple inputs |
--cov | CLI | Coverage (pytest-cov) |
-n auto | CLI | Parallel (pytest-xdist) |
Decision Tree
START
├── New project? → src layout + pyproject.toml
├── Testing async? → pytest-asyncio, asyncio_mode = "auto"
├── Need parallel? → pytest-xdist, -n auto
├── Need coverage? → pytest-cov, --cov
└── DEFAULT → pyproject.toml + conftest.py + pytest-cov
Step-by-Step Guide
1. Install pytest and plugins
[src1]
pip install pytest pytest-cov pytest-mock pytest-xdist
# For async: pip install pytest-asyncio
2. Configure pyproject.toml
[src1]
[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
pythonpath = ["src"]
addopts = ["-ra", "--strict-markers", "-v", "--tb=short"]
markers = [
"slow: marks tests as slow",
"integration: marks integration tests",
]
3. Create conftest.py
[src2]
import pytest
@pytest.fixture
def sample_data():
return {"name": "test", "value": 42}
@pytest.fixture(scope="session")
def db_connection():
conn = create_connection()
yield conn
conn.close()
4. Write parametrized tests
[src3]
@pytest.mark.parametrize("price,expected", [(100, 90), (200, 180), (0, 0)])
def test_calculate_discount(price, expected):
assert calculate_discount(price, discount=0.10) == expected
5. Configure coverage
[tool.coverage.run]
source = ["src"]
branch = true
[tool.coverage.report]
fail_under = 80
show_missing = true
Code Examples
Complete pyproject.toml
[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
pythonpath = ["src"]
addopts = ["-ra", "--strict-markers", "-v", "--cov=src", "--cov-report=term-missing", "--timeout=30"]
markers = ["slow: slow tests", "integration: integration tests", "e2e: end-to-end tests"]
asyncio_mode = "auto"
[tool.coverage.run]
source = ["src"]
branch = true
omit = ["*/tests/*"]
[tool.coverage.report]
fail_under = 80
show_missing = true
exclude_lines = ["pragma: no cover", "if __name__ == .__main__.", "if TYPE_CHECKING:"]
Advanced conftest.py
import pytest
@pytest.fixture
def make_user():
def _make_user(name="test_user", role="viewer"):
return {"name": name, "role": role}
return _make_user
@pytest.fixture(scope="session")
def database():
db = setup_test_database()
yield db
teardown_test_database(db)
@pytest.fixture(autouse=True)
def clean_state(database):
yield
database.rollback()
Parametrize with IDs
@pytest.mark.parametrize("input_data,expected", [
pytest.param({"key": "value"}, True, id="valid-dict"),
pytest.param({}, False, id="empty-dict"),
pytest.param(None, False, id="none-input"),
])
def test_validate_data(input_data, expected):
assert validate(input_data) == expected
Anti-Patterns
Wrong: Importing conftest.py directly
# ❌ BAD — conftest.py is auto-discovered
from tests.conftest import sample_data
Correct: Request fixtures as parameters
# ✅ GOOD — pytest injects fixtures automatically
def test_something(sample_data):
assert sample_data["name"] == "test"
Wrong: Higher-scoped fixture depending on lower-scoped
# ❌ BAD — ScopeMismatch error
@pytest.fixture(scope="session")
def database(tmp_path): # tmp_path is function-scoped!
Correct: Use session-compatible equivalents
# ✅ GOOD — tmp_path_factory is session-scoped
@pytest.fixture(scope="session")
def database(tmp_path_factory):
path = tmp_path_factory.mktemp("data")
return create_db(path)
Wrong: Misspelling parametrize
# ❌ BAD — silently ignored
@pytest.mark.parameterize("x", [1, 2, 3]) # Wrong!
Correct: British spelling without second 'e'
# ✅ GOOD
@pytest.mark.parametrize("x", [1, 2, 3])
Common Pitfalls
- ModuleNotFoundError: Fix:
pythonpath = ["src"]in pyproject.toml. [src2] - Fixture not found: Fix: place conftest.py in directory above test file. [src1]
- Unregistered marker warnings: Fix: add to markers list with
--strict-markers. [src4] - Slow tests blocking CI: Fix:
pytest-timeoutwithtimeout = 30. [src1] - Coverage too low: Fix:
source = ["src"]in coverage config. [src7] - Async tests skipped: Fix: install pytest-asyncio, set
asyncio_mode = "auto". [src6]
Diagnostic Commands
# Discover tests without running
pytest --collect-only
# Show fixtures
pytest --fixtures
# Run by keyword
pytest -k "test_auth"
# Run by marker
pytest -m "not slow"
# Show durations
pytest --durations=10
# HTML coverage
pytest --cov=src --cov-report=html
Version History & Compatibility
| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| pytest 8.x | Current | Collection changes, warns(None) deprecated | Use recwarn fixture |
| pytest 7.x | Maintenance | pythonpath added | Best upgrade from 6.x |
| pytest 6.x | EOL | pyproject.toml support | Migrate from pytest.ini |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Any Python project | JavaScript/TypeScript | Jest or Vitest |
| Complex fixture graphs | Minimal, no-dep testing | unittest (stdlib) |
| Parametrized testing | Load testing | locust or k6 |
Important Caveats
- Root conftest.py applies to ALL tests — be careful with autouse fixtures
- pytest-asyncio
automode auto-marks async tests — remove manual decorators --covin addopts runs coverage every time — consider CI-only- pytest-xdist (
-n auto) does not support--pdb