Run one pytest test against many inputs with parametrize
Use @pytest.mark.parametrize to run one test function against many input sets without duplicating code.
01 Aug 2026, 08:22 UTC

The problem: one test function, twenty versions of it
Imagine you are testing a function that calculates the final price after tax. You need to verify it for several price and tax-rate combinations. Without parametrization, you might write a separate test function for each combination, or loop inside a single test. The first approach duplicates setup and assertions. The second approach hides which combination failed: if the third iteration raises an AssertionError, pytest reports one failed test, not three distinct cases.
The thesis: declare inputs once, run many tests
pytest.mark.parametrize is a built-in decorator that feeds a test function multiple sets of arguments. Pytest treats each set as a separate test invocation with its own node ID—the unique label pytest assigns to every collected test. You keep one assertion block, but receive granular pass/fail results for every input.
Worked example: pricing calculator
Assume a module pricing.py contains:
def calculate_total(price: float, tax_rate: float) -> float:
return price * (1 + tax_rate)
Create test_pricing.py in your project root:
import pytest
from pricing import calculate_total
@pytest.mark.parametrize(
"price,tax_rate,expected",
[
(100.0, 0.05, 105.0),
(200.0, 0.10, 220.0),
(50.0, 0.00, 50.0),
],
)
def test_calculate_total(price, tax_rate, expected):
assert calculate_total(price, tax_rate) == expected
Run from the project root. No special permissions are required:
pytest test_pricing.py -v
The -v flag enables verbose output. You should see three parametrized test instances, such as test_calculate_total[100.0-0.05-105.0], followed by a summary line like 3 passed. If one case fails, pytest names the exact combination instead of a generic loop iteration.
Floating-point math can introduce tiny rounding differences. If your expected values are not exact, replace the assertion with assert calculate_total(price, tax_rate) == pytest.approx(expected).
Label cases with ids for readable failures
Long argument lists are hard to scan in test output. Add the ids parameter to give each case a readable name:
@pytest.mark.parametrize(
"price,tax_rate,expected",
[
(100.0, 0.05, 105.0),
(200.0, 0.10, 220.0),
(50.0, 0.00, 50.0),
],
ids=["standard-rate", "high-rate", "tax-exempt"],
)
def test_calculate_total(price, tax_rate, expected):
assert calculate_total(price, tax_rate) == expected
Pytest now displays test_calculate_total[standard-rate] instead of raw values, which makes continuous integration (CI) logs easier to debug.
Check the blast radius before running
Parametrization multiplies test cases. Before committing a large dataset, count how many tests pytest will collect without executing them:
pytest --collect-only -q
Review the count. If it is unexpectedly large, trim the dataset or split it across files to keep CI runtime predictable.
Trade-offs and limitations
- Test count explosion: A decorator with ten values for each of three arguments produces 1,000 test cases. Runtime grows linearly, and a single bad input can fail the whole suite in CI.
- Argument order: The names in the decorator string must match the test function parameters positionally. A mismatch raises
TypeErrorat collection time. - Readability: Long lists of tuples become hard to scan. Use
idsto label cases, or move complex data to an external file.
Actionable closing
Start with a small, fixed set of inputs that cover boundaries and common cases. Run pytest -v to confirm each case appears separately, then add --collect-only to your CI check if the dataset grows. Parametrization is not a replacement for fixtures or separate test functions, but it is the right tool when the same assertion applies to many inputs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.