TL;DR
you can skip on condition inside the test itself; skip to Suggestion at the end of the answer. With your example code, skipping tests via skipIf
markers is not possible.
First of all, let me clear the following: if you define an autouse fixture foo
that returns a value, it does not mean the test class will automatically become an attribute foo
.
class TestCls:
@pytest.fixture(autouse=True)
def fix(self):
return 'foo'
def test_fix(self):
assert self.fix == 'foo'
Running this test will fail:
def test_fix(self):
> assert self.fix == 'foo'
E AssertionError: assert <bound method TestCls.fix of <test_bacon.TestCls object at 0x105670cf8>> == 'foo'
E + where <bound method TestCls.fix of <test_bacon.TestCls object at 0x105670cf8>> = <test_bacon.TestCls object at 0x105670cf8>.fix
That's because that's not how fixtures work. If a fixture is returning something, you can access its return value if you add a parameter named same as the fixture, in the test function. This is a problem with classes because you can't pass fixtures as parameters to test class methods; overall, the support of test classes in pytest
is limited. Not sure why you need classes at all; you can perfectly share the state between multiple test functions using fixtures alone. The whole intention of pytest
is to make test classes obsolete, where every test method can be reworked to a test function.
If you need to share a value between tests in a class, you need to assign it to some attribute of the test class instance in the fixture. The example below will pass:
class TestCls:
@pytest.fixture(autouse=True)
def fix(self):
self._fix = 'foo'
def test_fix(self):
assert self._fix == 'foo'
Thus, fixtures that actually return something are useless in test classes, because there's no way to pass fixtures as arguments in test class methods.
Not sure why it is failing in class
That's because the markers are evaluated on tests collection to filter out tests that should be executed, and fixtures are executed only after the tests are collected and ready to run. This means what you want is not possible. You will not be able to pass any fixture result to skipif
marker as no fixture is evaluated yet. Check the execution order:
@pytest.fixture(autouse=True)
def myfixture():
print('myfixture called')
class TestCls:
@pytest.fixture(autouse=True)
def myclassfixture(self):
print('TestCls.myclassfixture called')
@pytest.mark.skipif('print(os.linesep, "TestCls.test_spam skipif called")', reason='something')
def test_spam(self):
print('TestCls.test_spam called')
Output:
TestCls.test_spam skipif called
myfixture called
TestCls.myclassfixture called
TestCls.test_spam called
Also note that the test class instance is not available in skipif
, only what's defined on module level (classes, functions and global variables):
class Tests:
@pytest.mark.skipif('print(os.linesep, "globals:", globals().keys(), os.linesep, "locals:", locals().keys())', reason='something')
def test_spam(self):
pass
Output:
test_spam.py::TestCls::test_spam
globals: dict_keys(['os', 'sys', 'platform', 'config', '__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', '@py_builtins', '@pytest_ar', 'pytest', 'myfixture', 'TestCls'])
locals: dict_keys(['os', 'sys', 'platform', 'config', '__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', '@py_builtins', '@pytest_ar', 'pytest', 'myfixture', 'TestCls'])
Suggestion
Besides the skip
/skipIf
markers, you can skip the test explicitly inside the test itself:
class TestCls:
@pytest.fixture(autouse=True)
def myfixture(self):
self.status = 'fail'
def test_eggs(self):
if self.status == 'fail':
pytest.skip('self.status is set to "fail"')
assert False
If run, the test is skipped:
test_eggs.py::TestCls::test_eggs SKIPPED