# Testing Use fixtures for reusable resource setup, parametrization for behavior matrices, and explicit boundary seams instead of ad hoc mocking. ## Why Good Python tests optimize for three things at once: - local readability - cheap variation across inputs and modes - reusable setup and cleanup without hiding intent The mature pattern is not just “use pytest.” It is: - model resources with fixtures - make fixture lifetime visible with `yield` when cleanup matters - use parametrization when one behavior should hold across several inputs - test through boundary seams like transports instead of patching internals blindly ## The pattern 1. Use fixtures for shared setup and resources. 2. Use `yield` fixtures when setup and teardown both matter. 3. Use parametrization when the assertion shape is the same but inputs vary. 4. Prefer explicit seams over invasive mocking. 5. Keep the test body focused on behavior, not scaffolding. ## When to use Use fixtures when: - multiple tests need the same resource wiring - setup or cleanup would otherwise dominate the body - the setup is a dependency, not the behavior under test Use parametrization when: - one behavior should hold across several inputs or modes - the data varies but the test story stays the same Use transport or injected seams when: - the behavior crosses I/O boundaries - you want realistic flow without spinning up the whole world ## When not to use Do not hide essential behavior behind a giant fixture tower. Do not parametrize cases that deserve different narratives or different assertions. Do not call fixtures directly like helper functions; if you want a helper, write a helper. Do not mock deep internals when a cleaner external seam exists. ## Preferred shapes ### Yield fixture for lifecycle ```python @pytest.fixture def resource(): obj = make_resource() yield obj obj.close() ``` This keeps setup and teardown obvious. ### Parametrization for behavior matrices ```python @pytest.mark.parametrize("mode", ["prepend", "append", "importlib"]) def test_import_behavior(mode: str) -> None: ... ``` One behavior, several inputs, no duplicated body. ### Boundary seam instead of monkeypatch soup ```python transport = httpx.MockTransport(handler) client = httpx.Client(transport=transport) ``` This is usually cleaner than patching internals in three places. ## Counterexamples ### Repeated setup in every test ```python def test_a(): client = make_client() tmpdir = make_tmpdir() seed_db() def test_b(): client = make_client() tmpdir = make_tmpdir() seed_db() ``` The scaffolding overwhelms the behavior. ### Fixture tower opacity If understanding the test requires opening six fixtures before reading one assertion, the abstraction has gone too far. ### Calling fixtures directly Pytest explicitly rejects this because fixtures are injected dependencies, not disguised utility functions. ## Source signals ### Pytest - `src/_pytest/fixtures.py:1378-1440` makes the contract explicit twice: calling a fixture directly is an error, and `yield` fixtures run teardown code after the test outcome. - `testing/test_threadexception.py:84-91` shows a real `yield` fixture with post-test cleanup work after the `yield`. - `testing/acceptance_test.py:158-169` uses `@pytest.mark.parametrize(...)` to check one behavior across multiple import modes without cloning the test body. - `testing/acceptance_test.py:561-574` shows another compact parametrized case where only the example data changes. ### HTTPX - `httpx/_transports/asgi.py:63-83` exposes `ASGITransport` as an in-process integration seam and even documents `raise_app_exceptions=False` for testing 500 responses. - `httpx/_transports/mock.py:15-43` exposes `MockTransport` as a first-class request/response seam for tests. - `httpx/_client.py:639-660` accepts `transport=` directly on `Client`, which is what makes transport substitution a normal testing path instead of a hack. ## Bottom line A good Python test makes the behavior easy to see and the environment cheap to vary. Use fixtures for lifetime. Use parametrization for variation. Use explicit seams instead of brittle patching.