Sharing Expensive Setup with Pytest Module‑Scoped Fixtures
Learn how to define a Pytest fixture with module scope to share expensive setup across tests, see a concrete example, and understand the limits and common pitfalls.
10 Feb 2026, 21:57 UTC

Use a module‑scoped fixture to create an expensive resource once per test module
When several tests in the same file need the same costly setup—for example, a database engine, a heavyweight object, or an external service connection—defining a fixture with scope='module' makes Pytest create that resource a single time for the whole module and tear it down after the last test finishes. This reduces test runtime and avoids redundant work.
Worked example
Place the fixture definition in a conftest.py file that lives next to your test module, or directly in the test file if you prefer to keep it local.
# conftest.py
import pytest
from sqlalchemy import create_engine
@pytest.fixture(scope='module')
def db_engine():
"""Create a SQLite in‑memory engine once per test module."""
engine = create_engine('sqlite:///:memory:')
try:
yield engine
finally:
engine.dispose()
# test_user.py
import pytest
def test_insert_user(db_engine):
with db_engine.connect() as conn:
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
conn.execute("INSERT INTO users (name) VALUES ('alice')")
result = conn.execute("SELECT name FROM users WHERE id = 1").fetchone()
assert result[0] == 'alice'
def test_user_count(db_engine):
with db_engine.connect() as conn:
count = conn.execute("SELECT COUNT(*) FROM users").scalar()
assert count == 1
The fixture db_engine is executed exactly once when Pytest collects test_user.py. Both tests receive the same engine instance, so the table created in the first test is still present when the second test runs.
How to verify the fixture runs once per module
- Run
pytest -v --setup-show test_user.py. The output shows a singleSETUPline fordb_engineand a singleTEARDOWNline after the last test, regardless of how many test functions are in the file. - Add a temporary print statement inside the fixture:
@pytest.fixture(scope='module')
def db_engine():
print('\n>>> DB ENGINE SETUP')
engine = create_engine('sqlite:///:memory:')
try:
yield engine
finally:
print('<<< DB ENGINE TEARDOWN')
engine.dispose()
When you execute pytest test_user.py, you will see the setup printed once before the first test and the teardown printed once after the last test.
Limits and common mistakes
- Shared mutable state: Because the fixture lives until the module finishes, any changes made to the resource in one test are visible to later tests. If a test mutates the database (e.g., inserts rows) and does not clean up, subsequent tests may see unexpected data. The usual remedy is to reset state inside each test or to use a function‑scoped fixture for the mutable part.
- Parallel test execution: When using
pytest‑xdistto run tests across multiple workers, each worker may import the test module independently, causing the module‑scoped fixture to be instantiated once per worker. This can lead to multiple engines and defeats the sharing goal. Either avoid module scope with‑‑dist=loadscopeor switch to a session‑scoped fixture combined with a lock if true sharing across workers is required. - Autouse side effects: Marking a module‑scoped fixture with
autouse=Truemakes it automatically available to every test in the module, and also to any test in a sibling module that imports the sameconftest.py. This can create hidden dependencies and make it harder to reason about which tests rely on the fixture. Use autouse sparingly and document the intent. - Parametrized tests: A module‑scoped fixture is invoked only once per module, not once per parameter set. If your parametrized test expects a fresh resource for each parameter value, the shared fixture may hide bugs. In that case, keep the fixture at function scope or create a separate fixture that depends on the module‑scoped one and resets it per parameter.
- Forgotten teardown: If the fixture yields a resource that requires explicit cleanup (e.g., closing a network socket) and you omit the
finallyblock or theyieldcleanup, the resource may leak, especially when the test suite runs many times in a CI pipeline. Always place cleanup code after theyieldor useaddfinalizer.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.