Taming parametrize with pytest.param

Redowan Delowar August 28, 2024
Source
I love [pytest.mark.parametrize] - so much so that I sometimes shoehorn my tests to fit into it. But the default style of writing tests with parametrize can quickly turn into an unreadable mess as the test complexity grows. For example: The polarify function converts Cartesian coordinates to polar coordinates. We're using @pytest.mark.parametrize in its standard form to test different conditions. Here, the list of nested tuples with inputs and expected values becomes hard to read as the test suite grows larger. When the function under test has a more complex signature, I find myself needing to do more mental gymnastics to parse the positional input and expected values inside parametrize. Also, how do you run a specific test case within the suite? For instance, what if you want to run only the third case where x, y, expected = (0, 1, (1, 1.5707963267948966))? I used to set custom test IDs like below to be able to run individual test cases within parametrize: This works, but mentally associating the IDs with the examples is cumbersome, and it doesn't make things any easier to read. TIL, [pytest.param] gives you a better syntax and more control to achieve the same. Observe: We're setting the unique IDs inside pytest.param. Now, any test can be targeted with pytest's -k flag like this: This will only run the second test case on the list. Or, This will run the last two tests. But the test is still somewhat hard to read. I usually refactor mine to take a kwargs argument so that I can neatly tuck all the input and expected values associated with a test case in a single dictionary. Notice: Everything associated with a single test case is passed to pytest.param in a single dictionary, eliminating the need to guess any positional arguments. Using pytest.param also allows you to set custom test execution conditionals, which I've started to take advantage of recently: In the last block, pytest.param bundles test data with execution conditions. We're using xfail to mark a test as expected to fail, while skipif skips tests based on conditions. This keeps all the logic for handling test cases, including failures and skips, directly alongside the test data. [pytest.mark.parametrize]: https://docs.pytest.org/en/7.1.x/how-to/parametrize.html#parametrize-basics [pytest.param]: https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest-param

Discussion in the ATmosphere

Loading comments...