Text parametrization is a great feature provided by pytest. We’ve touched it inside the Parametrize tests using pytest article. Anyhow, it’s also easy to make things unreadable when there are lots of examples or lots of parametrized values. We want to know why some examples are present. We’d also like to know that, three months later, when we get back to the code. Or if we’re reading someone else’s tests.
Name examples with pytest.param
Fortunately, pytest covers that as well with a very handy helper, pytest.param. We can use it to wrap an iterable of our parametrized values (it takes any number of positional arguments) and set its id. We can use any string for ID. That’s where we can put the example’s name. Doing that, it’s much easier to know why some example is there and which case it covers. Also, when executing the tests, pytest uses the ID when logging the executions. If the test with a certain example fails, we’ll see the ID as well. This way, we can locate the defect much quicker. Without pytest.param, we’d see actual values or just their types, which makes it much harder to understand what is wrong. See example with success URL validation below:
# test_named_parametrized_examples.py
import re
import pytest
REDIRECT_REGEX = r"(^/([A-Za-z0-9#&?=]+|$)|^https://my.domain\.com/[A-Za-z0-9#&?=/]*$)"
def is_valid_success_url(url: str) -> bool:
return bool(re.match(REDIRECT_REGEX, url))
@pytest.mark.parametrize(
"success_url",
[
pytest.param(
"/",
id="relative_root",
),
pytest.param(
"/some/path",
id="relative_path",
),
pytest.param(
"/?foo=bar",
id="relative_root_with_query_param",
),
pytest.param(
"/?foo=bar",
id="relative_path_with_query_param",
)
]
)
def test_is_valid_success_url(success_url: str):
assert is_valid_success_url(success_url) is True
@pytest.mark.parametrize(
"success_url",
[
pytest.param(
"https://somewhere-else.com",
id="non_whitelisted_domain",
),
pytest.param(
"https://somewhere-else.com",
id="non_whitelisted_domain",
),
pytest.param(
"https://mydomain.com@bad-boy.com/",
id="domain_change",
),
pytest.param(
"https://mydomain.com/settings>\r\nBCC:hrtstjyww456yeh@gsd.com\r\npda: m",
id="malicious_url",
),
pytest.param(
"//settings",
id="double_slash",
),
]
)
def test_not_valid_success_url(success_url: str):
assert is_valid_success_url(success_url) is False
# test_output.sh
test_named_parametrized_examples.py::test_is_valid_success_url[relative_root] PASSED [ 11%]
test_named_parametrized_examples.py::test_is_valid_success_url[relative_path] PASSED [ 22%]
test_named_parametrized_examples.py::test_is_valid_success_url[relative_root_with_query_param] PASSED [ 33%]
test_named_parametrized_examples.py::test_is_valid_success_url[relative_path_with_query_param] PASSED [ 44%]
test_named_parametrized_examples.py::test_not_valid_success_url[non_whitelisted_domain0] PASSED [ 55%]
test_named_parametrized_examples.py::test_not_valid_success_url[non_whitelisted_domain1] PASSED [ 66%]
test_named_parametrized_examples.py::test_not_valid_success_url[domain_change] PASSED [ 77%]
test_named_parametrized_examples.py::test_not_valid_success_url[malicious_url] PASSED [ 88%]
test_named_parametrized_examples.py::test_not_valid_success_url[double_slash] PASSED [100%]
======================================================================================================== 9 passed in 0.01s ========================================================================================================
(venv) someone@Rhodes some_folder %