Description
Writing parametrized tests is pretty annoying. The parameter values have to be passed in as a list of anonymous tuples, so you have to rely on the order alone to figure out what's what, and the comma-delineated string for the parameter values is just asking for trouble. It also leads to a lot of indentation and useless bracket- or parentheses-only lines after it goes through a code formatter like black.
Example:
@pytest.mark.parametrize(
argnames="flavour_prices,expected_revenue",
argvalues=[
(
{
"Vanilla": 1.50,
"Strawberry": 1.80,
"Chocolate": 1.80,
"Caramel": 1.65,
},
1_200_000,
),
(
{
"Vanilla": 1.25,
"Strawberry": 1.55,
"Chocolate": 1.65,
"Caramel": 2.10,
},
1_350_000,
),
],
ids=["Strategy A", "Strategy B",]
)
def test_ice_cream_projections(flavour_prices, expected_revenue):
... # test function here
And then, if you want labels for these, you have to pass in a separate list for their ids, and make sure to keep that properly aligned with the argnames and argvalues. Overall it just seems like an unnecessary amount of bother.
What I often find myself doing is writing some variant of this helper function:
class Case:
def __init__(self, label=None, /, **kwargs):
self.label = label
self.kwargs = kwargs
def nicer_parametrize(*args):
for case in args:
if not isinstance(case, Case):
raise TypeError(f"{case!r} is not an instance of Case")
first_case = next(iter(args))
first_attrs = first_case.kwargs.keys()
argument_string = ",".join(sorted(list(first_attrs)))
case_list = []
ids_list = []
for case in args:
case_dict = case.kwargs
attrs = case_dict.keys()
if attrs != first_attrs:
raise ValueError(
f"Inconsistent argument signature: {first_case!r}, {case!r}"
)
case_tuple = tuple(value for key, value in sorted(list(case_dict.items())))
case_list.append(case_tuple)
ids_list.append(case.label)
return pytest.mark.parametrize(
argnames=argument_string, argvalues=case_list, ids=ids_list
)
That lets me write the parametrized tests in a much nicer way:
@nicer_parametrize(
Case(
"Strategy A",
flavour_prices={
"Vanilla": 1.50,
"Strawberry": 1.80,
"Chocolate": 1.80,
"Caramel": 1.65,
},
expected_revenue=1_200_000,
),
Case(
"Strategy B",
flavour_prices={
"Vanilla": 1.25,
"Strawberry": 1.55,
"Chocolate": 1.65,
"Caramel": 2.10,
},
expected_revenue=1_350_000,
),
# can also do just Case(**kwargs) if no label is needed
)
def test_ice_cream_projections(flavour_prices, expected_revenue):
...
There's one less layer of indentation, it's more explicit what each part means, the order of the named arguments don't matter, and it's impossible to get the names, values, and labels all mixed up.
It would be nice if something like this were an official part of pytest.