Efficient Multi-Case Testing with Pytest's @pytest.mark.parametrize
Learn to use @pytest.mark.parametrize to run one test function across many inputs, combine with fixtures via indirect parametrization, and avoid combinatorial explosion and shared-state pitfalls.
12 Jul 2025, 11:57 UTC

Desired Outcome
Run a single test function against many input combinations without duplicating test code, while keeping test reports readable and failures traceable to specific parameter sets.
Prerequisites
- Python 3.8+ with pytest installed (
pip install pytest) - Basic familiarity with writing pytest test functions and fixtures
- A project layout where test files are discoverable (e.g.,
test_*.pyor*_test.py)
Core Parametrization Pattern
The @pytest.mark.parametrize decorator takes two arguments: a comma-separated string of parameter names and an iterable of argument tuples. Each tuple becomes one test invocation.
# test_math.py
import pytest
@pytest.mark.parametrize('a,b,expected', [
(1, 2, 3),
(4, 5, 9),
(0, -1, -1),
(100, 200, 300),
])
def test_add(a, b, expected):
assert a + b == expected
Run with pytest test_math.py -v. Pytest reports four distinct test cases: test_add[1-2-3], test_add[4-5-9], etc. The parameter values appear in the test name, making failures easy to locate.
Combining with Fixtures for Layered Scenarios
Fixtures can supply shared setup data, while parametrization selects subsets. Use indirect=True to pass raw values to a fixture that transforms them before the test runs.
# test_data_access.py
import pytest
@pytest.fixture
def dataset():
# Expensive setup: load once per session
return {'users': [10, 20, 30], 'admins': [100, 200]}
@pytest.mark.parametrize('key,index,expected', [
('users', 0, 10),
('users', 2, 30),
('admins', 1, 200),
], indirect=['key'])
def test_dataset_lookup(key, index, expected, dataset):
# The fixture 'key' receives each raw string and returns the list
assert key[index] == expected
# Fixture that transforms the raw parameter
@pytest.fixture
def key(request, dataset):
return dataset[request.param]
Here key is a fixture that receives each parametrized string ('users', 'admins') via request.param and returns the corresponding list. The test then indexes into that list.
Handling Complex Data Types
When parameters are dictionaries or objects, use indirect parametrization to convert them into usable fixtures.
# test_config.py
import pytest
from dataclasses import dataclass
@dataclass
class ServiceConfig:
host: str
port: int
timeout: float
@pytest.fixture
def config(request):
raw = request.param
return ServiceConfig(**raw)
@pytest.mark.parametrize('config', [
{'host': 'localhost', 'port': 8080, 'timeout': 5.0},
{'host': 'api.example.com', 'port': 443, 'timeout': 10.0},
], indirect=True)
def test_service_connection(config):
# Use config.host, config.port, config.timeout
assert config.port in (8080, 443)
Expected Checks
- Run
pytest -vand verify the test count matches the number of parameter tuples. - Confirm each test name includes the parameter values (e.g.,
test_add[4-5-9]). - Introduce a deliberate failure (change an expected value) and ensure the traceback points to the exact parameter set.
Limitations and Recovery Options
Combinatorial Explosion
Multiple @pytest.mark.parametrize decorators on the same function multiply the test count. For example, three parameters with 5, 4, and 3 values produce 60 tests. Mitigate by:
- Slicing the parameter list:
param_list[:10] - Using
pytest.mark.parametrizewith a generator that yields only needed combinations - Splitting into separate test functions for orthogonal concerns
Shared Mutable State
If parametrized tests modify global state (database, filesystem, in-memory caches), isolate each run:
- Use function-scoped fixtures (default) for per-test setup/teardown.
- Avoid session-scoped fixtures that hold mutable objects across parametrized runs.
- Consider
pytest-xdistfor parallel execution; ensure fixtures are thread-safe.
Debugging a Single Parameter Set
Run one specific case with pytest "test_math.py::test_add[4-5-9]" -v. The bracket syntax selects the exact parametrized instance.
Verification Checklist
- Test file executes without collection errors.
- Reported test count equals the number of provided parameter tuples.
- Parameter values appear in test names in the verbose output.
- A failing assertion shows the exact parameter combination in the traceback.
- Fixtures used with
indirect=Truereceiverequest.paramand return the transformed object.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.