pyproject.toml under [tool.pytest.ini_options]; use conftest.py for shared fixtures; register custom markers; use src layout with pythonpath = ["src"].pytest -v --tb=shortparametrize as parameterize — pytest silently ignores the decorator.conftest.py files are auto-discovered — never import them directlyparametrize is spelled without the second 'e'| 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) |
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
[src1]
pip install pytest pytest-cov pytest-mock pytest-xdist
# For async: pip install pytest-asyncio
[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",
]
[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()
[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
[tool.coverage.run]
source = ["src"]
branch = true
[tool.coverage.report]
fail_under = 80
show_missing = true
[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:"]
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()
@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
# ❌ BAD — conftest.py is auto-discovered
from tests.conftest import sample_data
# ✅ GOOD — pytest injects fixtures automatically
def test_something(sample_data):
assert sample_data["name"] == "test"
# ❌ BAD — ScopeMismatch error
@pytest.fixture(scope="session")
def database(tmp_path): # tmp_path is function-scoped!
# ✅ 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)
# ❌ BAD — silently ignored
@pytest.mark.parameterize("x", [1, 2, 3]) # Wrong!
# ✅ GOOD
@pytest.mark.parametrize("x", [1, 2, 3])
pythonpath = ["src"] in pyproject.toml. [src2]--strict-markers. [src4]pytest-timeout with timeout = 30. [src1]source = ["src"] in coverage config. [src7]asyncio_mode = "auto". [src6]# 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 | 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 |
| 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 |
auto mode auto-marks async tests — remove manual decorators--cov in addopts runs coverage every time — consider CI-only-n auto) does not support --pdb