Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pytest not able to skip testcase in a class via marker skipif

I am using pytest framework and want to skip testcase based on some condition. Below code is not skipping the test case.

import pytest
class TEST:
    @pytest.fixture(scope="function", autouse=True)
    def func_fixture(self):
        return "fail"

    @pytest.mark.skipif("self.func_fixture=='fail'")
    def test_setting_value(self):
        print("Hello I am in testcase")

When running, it is saying 0 testcases executed.

This func_fixture is very crucial for the testsuite. It performs many pre-requisites before starting the test.

If I remove class, and add rest of the functions with same syntax (after removing self), it works. Not sure why it is failing in class

like image 680
Sumit Avatar asked Aug 09 '18 07:08

Sumit


People also ask

How do I mark a test in Skip pytest?

The simplest way to skip a test is to mark it with the skip decorator which may be passed an optional reason . It is also possible to skip imperatively during test execution or setup by calling the pytest. skip(reason) function. This is useful when it is not possible to evaluate the skip condition during import time.

How do you use pytest markers?

To use markers, we have to import pytest module in the test file. We can define our own marker names to the tests and run the tests having those marker names. -m <markername> represents the marker name of the tests to be executed.

Which of the following decorator is used to skip a test unconditionally with pytest?

The skip() decorator can be used to skip over a test that need not be run at all.

Can we assign multiple markers in pytest?

Multiple custom markers can be registered, by defining each one in its own line, as shown in above example. You can ask which markers exist for your test suite - the list includes our just defined webtest and slow markers: $ pytest --markers @pytest. mark.


1 Answers

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
like image 74
hoefling Avatar answered Oct 15 '22 08:10

hoefling