Pytest Fixture Scope: Decision Guide and Trade-offs
A decision guide for selecting pytest fixture scopes, comparing function, class, module, and session options with trade-offs and a concrete implementation pattern for database engine reuse.
19 Jan 2026, 19:46 UTC

Choosing the Right Pytest Fixture Scope
When tests need shared setup or teardown, pytest lets you declare the fixture's scope. The scope determines how often the fixture is executed and how isolated each test remains. Picking the right scope balances the cost of creating the resource against the need to keep test state independent, which is essential for reliable CI runs and local debugging.
Decision and Constraints
The key constraints are setup cost (how expensive the fixture is to create) and required isolation (how strictly test state must be separated). If the fixture is cheap and tests must be completely independent, a narrower scope is preferred. If the fixture is expensive and can safely share immutable data, a broader scope reduces repetition without introducing coupling.
Supported Options
| Scope | When to use | Isolation level | Setup cost |
|---|---|---|---|
| function | default; each test needs its own instance | strongest (new object per test) | highest (runs before every test) |
| class | multiple tests share the same object but still need per-test reset | moderate (one instance per class) | medium (runs once per class) |
| module | tests in the same file can reuse the same setup | moderate (one instance per module) | low (runs once per module) |
| session | entire test run shares a heavyweight resource | weakest (state persists across tests) | lowest (runs once per session) |
Trade-offs
Function scope provides the highest isolation because a fresh fixture instance is created for every test. The downside is repeated execution of the setup code, which can become a performance bottleneck when the fixture is expensive to initialize.
Class scope runs the fixture once for the whole class, cutting overhead while still delivering a separate instance for each test method. It works well when the fixture is stateless or can be reset between methods without heavy cost.
Module scope shares the fixture across all tests in a module. This reduces setup frequency but requires manual resetting of mutable objects to prevent cross-test contamination. It is a good middle ground when the resource is inexpensive.
Session scope creates the fixture a single time for the entire test run. It is the fastest, yet any mutation of shared objects persists, making tests order-dependent and harder to debug. Use it only when the resource is effectively immutable or when you can guarantee reset logic.
Concrete Implementation
Consider a database engine that must be created using a connection string. The engine creation is relatively expensive, so we declare it at session scope. Function-scoped fixtures then provide per-test isolation by yielding a fresh connection or re-initializing the engine as needed:
@pytest.fixture(scope='session')
def db_engine(): engine = create_engine('') yield engine engine.dispose()Function-scoped fixtures that depend on db_engine can re-create the connection for each test, ensuring that tests do not share mutable state while still reusing the expensive engine creation:
@pytest.fixture
def db_connection(db_engine): conn = db_engine.connect() yield conn conn.close()Verification
- Add a simple counter inside the fixture (e.g., a global variable incremented in the yield point). Run pytest -v. The counter should reflect the expected number of invocations for the chosen scope (e.g., once for session, once per class, etc.).
- Run the suite with the default collection order and then with the collection order reversed to expose order-dependent failures caused by shared module or session state.
- Execute pytest --fixtures. The output lists each fixture with its declared scope; confirm that the scope attribute aligns with the intended sharing boundaries.
No special permissions are required; the commands run as the user executing pytest. After running the commands, compare the counter output with the numbers shown in the table to confirm correctness. If you observe more invocations than expected, revisit the fixture definitions or the collection order.
In practice, start with function scope for most tests, move to class or module when setup cost is high, and only use session scope for truly heavyweight, immutable resources.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.