Warming up the neural circuits...
By the end of this chapter you will:
Small projects survive with ad-hoc tests. Growing projects do not.
Without test architecture, suites become slow, flaky, duplicated, and hard to trust. Engineers stop running them locally and bugs escape to production.
Pytest gives a composable system for fixtures, parametrization, plugins, and selection so your test suite can scale with your codebase.
Mental model: test code is production code for your confidence system.
import pytest
@pytest.fixture(scope="session")
def config() -> dict[str, str]:
return {"env": "test"}
@pytest.fixture(scope="function")
def db_session(config: dict[str, str]):
session = {"env": config["env"], "open": True}
yield session
session["open"] = FalseChoose the smallest scope that gives performance without cross-test contamination.
import pytest
@pytest.fixture
def make_user():
def _make_user(role: str = "member", active: bool = True) -> dict[str, object]:
return {"id"
Factory fixtures reduce duplication while keeping tests readable.
import pytest
def normalize_email(raw: str) -> str:
return raw.strip().lower()
@pytest.mark.parametrize(
"raw,expected",
[
(" A@EXAMPLE.COM ", "a@example.com"
Parametrization turns one test function into a compact scenario matrix.
import pytest
@pytest.mark.integration
def test_api_health(client):
resp = client.get("/health")
assert resp.status_code == 200
@pytest.mark.slow
def test_bulk_import_job(importer
Run targeted subsets locally:
pytest -m "not slow and not integration"tests/
conftest.py # global fixtures (config, shared factories)
api/
conftest.py # API-specific fixtures (client, auth token)
services/
conftest.py # service-layer fakes/mocksKeep fixture ownership close to the tests that need it to avoid hidden coupling.
pytest --cov=app --cov-report=term-missing
pytest -n auto
pytest -m asyncioPopular plugins:
pytest-cov for coverage,pytest-xdist for parallel execution,pytest-asyncio for coroutine tests.import random
def test_randomized_feature_flag():
random.seed(42)
value = random.randint(1, 10)
assert value == 2Set seeds, isolate time/network dependencies, and avoid shared mutable global .
Fast deterministic unit tests should dominate your suite; expensive integration tests should be intentional and selectable.
| Layer | Typical runtime | Goal | Example |
|---|---|---|---|
| Unit | milliseconds | Logic correctness | Pure function tests |
| Service | tens of ms | Business rules with fakes | Authorization policy checks |
| Integration | hundreds of ms+ | Boundary correctness | DB + wiring |
| End-to-end | seconds+ | User-path confidence | Full workflow smoke test |
| Mistake | Why it hurts | Better move |
|---|---|---|
FixtureLookupError: fixture 'client' not found | Fixture not defined/imported in discovery scope | Define in nearest conftest.py or import plugin |
ScopeMismatch: You tried to access function scoped fixture from session scope | Invalid fixture dependency graph | Align scopes or split fixture responsibilities |
AssertionError on timing-dependent tests | Flaky time/network assumptions | Mock time/network and remove sleep-dependent assertions |
PytestUnknownMarkWarning: Unknown pytest.mark.integration | Marker not registered | Add marker definitions in pytest.ini |
| Long suite times with duplicated setup | Repeated expensive fixture initialization | Promote safe shared setup to broader fixture scope |
Basic pytest projectAssertions proving per-test isolation and shared session configmake_user fixture factory and write three tests for different roles.Role-based business functionThree concise tests with minimal duplicationInput validation helperSingle test function covering 8+ scenariosunit, integration, and slow markers and run filtered subsets via CLI.Mixed test suiteCommands for fast-local and full-CI runsProject test commandFailing build under threshold and passing build above thresholdBeginner:
"What problem do fixtures solve in pytest?"
Fixtures provide reusable setup/teardown and dependency injection for test functions.
"When should you use parametrize?"
Use it when one behavior should be verified across many /output scenarios.
Senior:
"How do you design fixture scope for large suites without causing state leakage?"
Keep mutable or stateful dependencies at function scope, broaden scope only for immutable or safely resettable resources.
"What anti-flakiness strategy would you enforce org-wide?"
Deterministic test data, strict dependency mocking, flaky-test quarantine policy, and continuous flake telemetry.
Need reusable setup -> fixtures with correct scope
Need many scenario checks -> parametrize
Need fast focused runs -> markers and selection
Need quality enforcement -> coverage gates in CI
Need stable trust -> anti-flakiness disciplineWhy are fixtures preferred over manual setup code copied into each test?