The way parametrized tests work in pytest
annoys me:
import pytest
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
assert eval(test_input) == expecteds
Passing in argument names as a comma-separated string just looks ugly and un-Pythonic, and you have to give each case as an anonymous tuple and make sure to get the order right every time. This becomes harder the more parameters you have. It's also a bit awkward to specify the test IDs. Even when you use pytest.param
there's still nothing stopping you from getting the order of the parameters wrong.
So, I wrote a simple wrapper around pytest.mark.parametrize
which makes it a bit nicer to read and use. The previous example would be written:
from pytest_parametrize_cases import Case, parametrize_cases
@parametrize_cases(
Case(test_input="3+5", expected=8),
Case(test_input="2+4", expected=6),
Case(test_input="6*9", expected=42)
)
def test_eval(test_input, expected):
assert eval(test_input) == expected
It also supports optional test IDs. Using the same example as the one in the pytest docs for pytest.mark.parametrize
, this is how you include them:
from datetime import datetime, timedelta
from pytest_parametrize_cases import Case, parametrize_cases
@parametrize_cases(
Case(
"forward",
a=datetime(2001, 12, 12),
b=datetime(2001, 12, 11),
expected=timedelta(1),
),
Case(
"backward",
a=datetime(2001, 12, 11),
b=datetime(2001, 12, 12),
expected=timedelta(-1),
),
)
def test_timedistance_v4(a, b, expected):
diff = a - b
assert diff == expected
That is to say, you decorate your test function with @parametrize_cases
and pass in multiple instances of Case
as arguments. The arguments to the Case
constructor are [optionally] a positional-only string which constitutes the test ID (printed when you use the -v / --verbose
flag), and then keyword arguments for each of the parameters the test function expects.
In my opinion this is much more readable and user-friendly than the default way of writing parametrized tests. Related data is kept together rather than spread out in multiple containers. Specifying the keyword arguments is mandatory, so it is always clear where each piece of data ends up in the test function (explicit is better than implicit). And it is more convenient to specify test IDs.
The parametrize_cases
decorator can be stacked multiple times to give the Cartesian product of your parametrizations, in the exact same way as mark.parametrize
.
- Python >= 3.8
pytest
pip install pytest-parametrize-cases
Or just look in src/pytest_parametrize_cases/case.py
and copypaste the two definitions to somewhere in your test suite.