Python Test Systems
Eight pytest-centered guides for fixture design, parametrization, async isolation, property tests, parallel workers, and AI-assisted review.
Series Chapters (8)
Design pytest Fixtures as Explicit Resource Contracts
A test that passes alone and errors in the full suite is almost never a flaky test — it is a fixture whose lifetime is wider than the state it holds, requested by nothing the test names. This guide works from what pytest actually reads when it orders fixtures, why one autouse fixture pulls its whole dependency graph into every test in scope, and why teardown after yield covers only the fixtures that already succeeded.
Use pytest Parametrization Without Hiding Test Intent
The generated ID is the only part of a parametrized case that reaches the report, the selection flag and the failure history — so an auto-generated one costs you both diagnosis and reproduction. This guide covers the exact rule pytest uses to build IDs, the default that silently renumbers collisions into positions, the effects that fire at collection time because a parameter table is just an expression in a decorator, and how to choose equivalence classes instead of Cartesian products.
Debug pytest-asyncio Event-Loop and Fixture Failures
pytest-asyncio gives one event loop per pytest collector, so every async resource is bound to the loop of whatever created it. That single fact explains attached to a different loop, Event loop is closed, ScopeMismatch on a fixture you never wrote, and the AttributeError on an async_generator that mentions no loop at all. This guide separates the five causes, gives a check that tells them apart, and starts with the two configuration defaults that do not match.
Run pytest in Parallel Without Shared-State Flakes
Adding workers does not create shared state — it reveals that the suite only ever worked because it ran one test at a time. This guide starts from the fact that every xdist worker collects independently and runs its own copy of each session-scoped fixture, then covers deriving a reclaimable namespace from worker_id, one PostgreSQL schema per worker, why collection order has to agree across workers, and why -s cannot work under xdist at all.
Property-Based Testing in Python: Start with Invariants
A property test asserting isinstance(encode(s), str) can run sixty thousand inputs and establish nothing, because no input in the domain could falsify it. This guide covers the three families of claim that can actually fail, why the default text alphabet is a decision rather than a default, constraining the domain in the strategy instead of with assume(), the correctness health check that catches function-scoped fixtures not resetting per input, and how to pin every shrunk counterexample so it never has to be rediscovered.
Build Deterministic Python API Test Data Factories
A duplicate-key violation blamed on parallelism, an assertion broken by a Faker patch bump, and a 41-line payload diff with two relevant fields all come from the same cause: generating data without deciding what is fixed, what varies, and what the test asserts. This guide covers the reseed-to-zero behaviour of the faker fixture, why generated values must never appear as expected outputs, deriving reproducible unique identity from the test node id with uuid5, and the jsonschema format checker that does nothing unless you switch it on.
Test Python Retry Logic Without Sleeping
In tenacity the sleeper is a constructor argument, so a retry test never needs to patch time.sleep or wait real seconds — pass a callable that records the delay and the list it collects is the backoff schedule. This guide covers asserting the schedule instead of the call count, bounding jittered waits using the documented formula rather than pinning the random source, enforcing a deadline against a clock the test owns, and why the exception your caller catches is not the one your code raised.
Review AI-Generated pytest Tests with Mutation Checks
A generated test file can execute every branch of a pricing rule and still pass when the threshold moves by one, because coverage records that a line ran and nothing records that an assertion depended on it. This guide covers scoping a mutmut run to a single function, telling a surviving mutant apart from a mutant that any calling test kills, the two review patterns — a mock placed one layer too low and an assertion that restates its own fixture — that make every mutant survive, and the pragmas and settings that quietly remove code from the run.