Migration from unittest-style tests with setUp
methods to pytest fixtures can
be laborious, because users have to specify fixtures parameters in each
test method in class. Also flake8
checks will complain about
unknown methods in parameters (it's minor issue, but it's still exists).
This article demonstrates alternatives way for an easier migration, with the
following benefits:
The main feature used to solve this issue is autouse fixtures. The detailed explanation is available in official documentation, but shortly: it allows to execute fixture before each test or test method in the current case.
The first approach uses a new decorator for test classes.
This decorator injects an autouse fixture in the decorated class; this fixture
will then be called automatically before each test.
Thanks to Bruno Oliveira
(nicoddemus) for the help with creation this
solution.
from functools import partial
import pytest
def _inject(cls, names):
@pytest.fixture(autouse=True)
def auto_injector_fixture(self, request):
for name in names:
setattr(self, name, request.getfixturevalue(name))
cls.__auto_injector_fixture = auto_injector_fixture
return cls
def auto_inject_fixtures(*names):
return partial(_inject, names=names)
@auto_inject_fixtures('tmpdir')
class Test:
def test_foo(self):
assert self.tmpdir.isdir()
More importantly, this also works for unittest subclasses:
@auto_inject_fixtures('tmpdir')
class Test2(unittest.TestCase):
def test_foo(self):
self.assertTrue(self.tmpdir.isdir())
As it was demonstrated fixtures names passed to the decorator are now available to test methods as instance attributes.
Consider the scenario where user has more then 5 or 10 classes and they all inherited from a main TestBase
class?
The answer is to take the first approach and modify it to do all hard
work with decorators automatically.
First put the autouse fixture in the TestBase
class, which
will make it available to all child classes automatically.
A second step is to declare a list of fixtures that will be injected in each
test class. In the ideal world they
will be the same for all classes and tests, but in reality it will be
different for each class. Define a tuple fixture_names
in each test class with the names of the fixtures that should be injected for each test.
The example below demostrates ideas mentioned before.
class TestBase(unittest.TestCase):
fixture_names = ()
@pytest.fixture(autouse=True)
def auto_injector_fixture(self, request):
names = self.fixture_names
for name in names:
setattr(self, name, request.getfixturevalue(name))
class MyTest(TestBase):
fixture_names = ('tmpdir', 'random')
def test_bar(self):
self.assertTrue(self.tmpdir.isdir())
self.assertEqual(len(self.random.string(5)), 5)
Important to mention that the approach above also work for pytest-style classes (subclassing only object
).
Last example can be improved for scenario tests. However the guide mentioned in the official documentation is not compatible with unittests subclasses.
class TestBase(object):
fixture_names = ()
@pytest.fixture(autouse=True)
def auto_injector_fixture(self, request):
if hasattr(self, 'scenarios'):
names = self.scenarios[0][1].keys()
else:
names = self.fixture_names
for name in names:
setattr(self, name, request.getfixturevalue(name))
class MyTestScenario(TestBase):
scenarios = [
(test_one, dict(val=1, res=5)),
(test_two, dict(val=2, res=10))
]
def test_baz(self):
assert self.res == self.val * 5
COMMENTS