How to skip parametrized tests with pytest
Question:
Is it possible to conditionally skip parametrized tests?
Here’s an example:
@pytest.mark.parametrize("a_date", a_list_of_dates)
@pytest.mark.skipif(a_date > date.today())
def test_something_using_a_date(self, a_date):
assert <some assertion>
Of course I can do this inside the test method, but I’m looking for a structured way to do this with pytest
.
Answers:
It is possible to do this, although depending on exactly what you want, it might take some work.
If you just need to skip specific parameter sets (i.e. if you don’t need to use an expression to identify which parameter sets to skip), it’s pretty easy:
@pytest.mark.parametrize("a", [
1,
pytest.param(2, marks=[pytest.mark.skip]),
])
def test_a(a):
assert a == 1
If you do need to use an expression, then I think the best approach is to write a custom pytest_runtest_setup
hook. This hook has access to the marks and parameters for each test, so it’s a good place to implement the kind of logic you want. The basic idea is to get the skip condition from a custom mark, evaluate that condition in the context of the parameters, then skip based on the result:
# conftest.py
import pytest
def pytest_runtest_setup(item):
skip_funcs = [
mark.args[0]
for mark in item.iter_markers(name='parametrize_skip_if')
]
if any(f(**item.callspec.params) for f in skip_funcs):
pytest.skip()
# test_file.py
@pytest.mark.parametrize("b", [1, 2])
@pytest.mark.parametrize_skip_if(lambda b: b == 2)
def test_b(b):
assert b == 1
If you create you own method you check the values in test collection time and run the relevant tests only
a_list_of_dates = [date.today(), date(2024, 1, 1), date(2022, 1, 1)]
def get_dates():
for d in a_list_of_dates:
if d <= date.today():
yield d
class TestSomething:
@pytest.mark.parametrize("a_date", get_dates())
def test_something_using_a_date(self, a_date):
print(a_date)
Output
TestSomething::test_something_using_a_date[a_date0] PASSED [ 50%] 2022-08-24
TestSomething::test_something_using_a_date[a_date1] PASSED [100%] 2022-01-01
If you still want to the the skipped tests you can add the skip
marker to the relevant tests
def get_dates():
for d in a_list_of_dates:
markers = []
if d > date.today():
markers.append(pytest.mark.skip(reason=f'{d} is after today'))
yield pytest.param(d, marks=markers)
Output
TestSomething::test_something_using_a_date[a_date0] PASSED [ 33%] 2022-08-24
TestSomething::test_something_using_a_date[a_date1] SKIPPED (2024-01-01 is after today) [ 66%]
Skipped: 2024-01-01 is after today
TestSomething::test_something_using_a_date[a_date2] PASSED [100%] 2022-01-01
Is it possible to conditionally skip parametrized tests?
Here’s an example:
@pytest.mark.parametrize("a_date", a_list_of_dates)
@pytest.mark.skipif(a_date > date.today())
def test_something_using_a_date(self, a_date):
assert <some assertion>
Of course I can do this inside the test method, but I’m looking for a structured way to do this with pytest
.
It is possible to do this, although depending on exactly what you want, it might take some work.
If you just need to skip specific parameter sets (i.e. if you don’t need to use an expression to identify which parameter sets to skip), it’s pretty easy:
@pytest.mark.parametrize("a", [
1,
pytest.param(2, marks=[pytest.mark.skip]),
])
def test_a(a):
assert a == 1
If you do need to use an expression, then I think the best approach is to write a custom pytest_runtest_setup
hook. This hook has access to the marks and parameters for each test, so it’s a good place to implement the kind of logic you want. The basic idea is to get the skip condition from a custom mark, evaluate that condition in the context of the parameters, then skip based on the result:
# conftest.py
import pytest
def pytest_runtest_setup(item):
skip_funcs = [
mark.args[0]
for mark in item.iter_markers(name='parametrize_skip_if')
]
if any(f(**item.callspec.params) for f in skip_funcs):
pytest.skip()
# test_file.py
@pytest.mark.parametrize("b", [1, 2])
@pytest.mark.parametrize_skip_if(lambda b: b == 2)
def test_b(b):
assert b == 1
If you create you own method you check the values in test collection time and run the relevant tests only
a_list_of_dates = [date.today(), date(2024, 1, 1), date(2022, 1, 1)]
def get_dates():
for d in a_list_of_dates:
if d <= date.today():
yield d
class TestSomething:
@pytest.mark.parametrize("a_date", get_dates())
def test_something_using_a_date(self, a_date):
print(a_date)
Output
TestSomething::test_something_using_a_date[a_date0] PASSED [ 50%] 2022-08-24
TestSomething::test_something_using_a_date[a_date1] PASSED [100%] 2022-01-01
If you still want to the the skipped tests you can add the skip
marker to the relevant tests
def get_dates():
for d in a_list_of_dates:
markers = []
if d > date.today():
markers.append(pytest.mark.skip(reason=f'{d} is after today'))
yield pytest.param(d, marks=markers)
Output
TestSomething::test_something_using_a_date[a_date0] PASSED [ 33%] 2022-08-24
TestSomething::test_something_using_a_date[a_date1] SKIPPED (2024-01-01 is after today) [ 66%]
Skipped: 2024-01-01 is after today
TestSomething::test_something_using_a_date[a_date2] PASSED [100%] 2022-01-01