Centralize Setup to Eliminate Repetition
Repeated setup code—like creating test data or DB connections across tests—leads to maintenance nightmares and fragility. Pytest fixtures solve this by defining reusable setup once, injected automatically into tests via function arguments.
Start with a basic fixture for shared data:
def test_addition(): result = add(2, 3) assert result == 5
@pytest.fixture
def sample_data():
return [1, 2, 3, 4]
def test_sum(sample_data):
result = sum(sample_data)
assert result == 10
Here, sample_data runs once per test, avoiding copy-paste. Fixtures support dependency chaining: a db_connection fixture can depend on test_user to build layered setups like def db_connection(test_user): return connect_db(test_user).
This keeps tests focused on assertions, cutting boilerplate by 50-80% in growing suites.
Scale with Parameters, Autouse, and Scopes
Parametrize fixtures for data-driven tests without exploding function counts:
@pytest.fixture(params=[(2,3,5), (0,0,0), (-1,1,0)])
def add_inputs(request):
return request.param
def test_addition(add_inputs):
a, b, expected = add_inputs
assert add(a, b) == expected
Runs the test three times with different inputs, covering edge cases efficiently.
Use autouse=True for global setup like patching or mocks:
@pytest.fixture(autouse=True)
def mock_time(monkeypatch):
monkeypatch.setattr('time.time', lambda: 1234567890)
Applies to all tests in the scope without explicit requests.
Control reuse with scope: function (default, per test), class (per class), module (per file, ideal for DB init), session (once per run, for expensive resources). Module scope on a DB fixture shares one connection across 20+ tests, reducing overhead from 2s to 0.2s per run.
Handle Teardown with Yield for Reliable Cleanup
Fixtures with yield enable post-test cleanup:
@pytest.fixture
def temp_file(tmp_path):
path = tmp_path / 'test.txt'
path.write_text('initial content')
yield str(path)
path.unlink()
def test_file_write(temp_file):
with open(temp_file, 'a') as f:
f.write('appended')
# File auto-deleted after
Code before yield sets up; after runs teardown. Perfect for temp files, DB rollbacks, or API mocks—ensures isolation even on failures, preventing leaks in CI runs.