Skip to main content

Command Palette

Search for a command to run...

@pytest.mark.parametrize: Enhancing clarity with "wrapped" parametrized arguments

Updated
โ€ข2 min read

One of the Pytest features I like and use most is @pytest.mark.parametrize. Below is an example of how to use it (from Pytest documentation):

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) == expected

Scenario

For tests where there are one or two parametrized arguments only, the names and values for the arguments are easily readable. However, the complexity can increase when there are several parametrized arguments and, even more when fixtures are passed to the test function as well. In such cases, it is not immediate to map the value to the argument for a given input when reading the code. In the example below we can notice that the test signature is longer (as expected) and there is no clear distinction between the parametrized arguments and the fixtures passed in.

import pytest

@pytest.fixture()
def dummy():
    # Dummy fixture
    return


def eval_math_operation(x: int, operator: str, y: int) -> int:
    # Dummy function just for illustration purposes
    return eval(f"{x} {operator} {y}")


@pytest.mark.parametrize("x, operator, y, expected", (
        (1, "+", 2, 3),
        (1, "-", 2, -1),
        (1, "*", 2, 2),
        (1, "/", 2, 0.5),
))
def test_eval_math_operation(x, operator, y, expected, dummy):
    assert eval_math_operation(x, operator, y) == expected

An alternative approach

To enhance clarity on the inputs of the tests, I usually prefer to wrap each set of parametrized arguments into a dictionary so "what would be" the parametrized arguments becomes a single one.

import pytest 

@pytest.fixture()
def dummy():
    # Dummy fixture
    return


def eval_math_operation(x: int, operator: str, y: int) -> int:
    # Dummy function just for illustration purposes
    return eval(f"{x} {operator} {y}")

@pytest.mark.parametrize("test_case", (
        dict(x=1, operator="+", y=2, expected=3),
        dict(x=1, operator="-", y=2, expected=-1),
        dict(x=1, operator="*", y=2, expected=2),
        dict(x=1, operator="/", y=2, expected=0.5),
))
def test_eval_math_operation(test_case, dummy):
    assert eval_math_operation(
        x=test_case["x"],
        operator=test_case["operator"],
        y=test_case["y"]
    ) == test_case["expected"]

That way, it is easier to understand not only the arguments in each test case but also the test signature, wherein it is now clear the distinction between the single test's parametrized argument and any additional fixtures. The test runner doesn't complain and the others reading the code may thank you! ๐Ÿ˜„