Reputation: 103
I am trying to reuse some unit testing functionality. The only variable part is actually the set of test data. Therefore I encapsulate the testing logic in a class and inject the actual test data at object creation.
Unfortunately this does not seem to work with the @pytest.fixture
decorator.
Some minimal code to reproduce.
This is the common definition of my test class in define_test.py
:
#!/usr/bin/env python3
import pytest
class TestDemo:
def __init__(self, _data: list):
self.__testData = _data
@pytest.fixture(scope='module', params=self.__testData)
def fixture(self, request):
return request.param
def test(self, fixture):
foo = fixture
assert foo is not None
Intended usage would be something like this in use_test.py
#!/usr/bin/env python3
import define_test
data1 = [1, 2, 3, 4]
test1 = define_test.TestDemo(data1)
data2 = [4, 3, 2, 1]
test2 = define_test.TestDemo(data2)
Trying to run this code produces
define_test.py:9: in TestDemo
???
E NameError: name 'self' is not defined
I am pretty sure that the problem is, that one cannot simply apply the usual OO mechanisms to the pytest
decorator. It seems as either the decorator is evaluated before even an instance of this class exists or it cannot handle instances at all.
So what would be the correct way of achieving the goal of reusable tests with variable data?
Upvotes: 1
Views: 1956
Reputation: 5992
You might be able to express this by dynamic parameterization which you can access through the hook pytest_generate_tests
there are some examples in the docs
This way you can intercept the tests at collection time and inject your own parameterize settings. The hook is called for each test with a metafunc
object as the argument which exposes a parameterize
method
The metafunc documentation is here
It doesn't look to me like you will be able to access instance data, because this code is happening at collect time, before your objects are initialised. But you can access the class object via metafunc.cls
, so maybe you could have your methods in a superclass, and then subclass this to add specific data cases as class attributes
here is some example code of the kind of thing I am thinking about
a) pytestfoo.py
#!/usr/bin/env python3
import pytest
class BaseTest:
def test(self, testparams):
print("\n", testparams, "\n")
assert testparams is not None
class TestCaseA(BaseTest):
testparams = [[1,2,3,4]]
class TestCaseB(BaseTest):
testparams = [[4,3,2,1]]
b) the corresponding conftest.py
def pytest_generate_tests(metafunc):
metafunc.parametrize("testparams", metafunc.cls.testparams)
and a run of these tests using pytest
pytest -sv pytestfoo.py
========================================================== test session starts ==========================================================
platform linux -- Python 3.7.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- /home/cms/.pyenv/versions/3.7.6/envs/tmp/bin/python3.7
cachedir: .pytest_cache
rootdir: /home/cms/tmp
collected 2 items
pytestfoo.py::TestCaseA::test[testparams0]
[1, 2, 3, 4]
PASSED
pytestfoo.py::TestCaseB::test[testparams0]
[4, 3, 2, 1]
PASSED
=========================================================== 2 passed in 0.01s ===========================================================
hopefully there is some inspiration here for your own experiments. The pytest docs are showing how to paramaterize with data from the command line, which might be more practically useful than the approach above, but I was trying to follow your lead in defining the data with code.
Upvotes: 2