Background
We have a big suite of reusable test cases which we run in different environments. For our suite with test case ...
@pytest.mark.system_a
@pytest.mark.system_b
# ...
@pytest.mark.system_z
test_one_thing_for_current_system():
assert stuff()
... we execute pytest -m system_a
on system A, pytest -m system_b
on system B, and so on.
Goal
We want to parametrize multiple test case for one system only, but neither want to copy-paste those test cases, nor generate the parametrization dynamically based on the command line argument pytest -m
.
Our Attempt
Copy And Mark
Instead of copy-pasting the test case, we assign the existing function object to a variable. For ...
class TestBase:
@pytest.mark.system_a
def test_reusable_thing(self):
assert stuff()
class TestReuse:
test_to_be_altered = pytest.mark.system_z(TestBase.test_reusable_thing)
... pytest --collect-only
shows two test cases
TestBase.test_reusable_thing
TestReuse.test_to_be_altered
However, pytest.mark
on one of the cases also affects the other one. Therefore, both cases are marked as system_a
and system_z
.
Using copy.deepcopy(TestBase.test_reusable_thing)
and changing __name__
of the copy, before adding mark
does not help.
Copy And Parametrize
Above example is only used for illustration, as it does not actually alter the test case. For our usecase we tried something like ...
class TestBase:
@pytest.fixture
def thing(self):
return 1
@pytest.mark.system_a
# ...
@pytest.mark.system_y
def test_reusable_thing(self, thing, lots, of, other, fixtures):
# lots of code
assert stuff() == thing
copy_of_test = copy.deepcopy(TestBase.test_reusable_thing)
copy_of_test.__name__ = "test_altered"
class TestReuse:
test_altered = pytest.mark.system_z(
pytest.mark.parametrize("thing", [1, 2, 3])(copy_of_test)
)
Because of aforementioned problem, this parametrizes test_reusable_thing
for all systems while we only wanted to parametrize the copy for system_z
.
Question
How can we parametrize test_reusable_thing
for system_z
...
- without changing the implementation of
test_reusable_thing
, - and without changing the implementation of fixture
thing
, - and without copy-pasting the implementation of
test_reusable_thing
- and without manually creating a wrapper function
def test_altered
for which we have to copy-paste requested fixtures only to pass them toTestBase().test_reusable_thing(thing, lots, of, other, fixtures)
.
Somehow pytest has to link the copy to the original. If we know how (e.g. based on a variable like __name__
) we could break the link.
You can defer the parametrization to the
pytest_generate_tests
hookimpl. You can use that to add your custom logic for implicit populating of test parameters, e.g.A demo example to pass the marker name to
test_reusable_thing
via asystem
arg:Running this will yield four tests in total: