Search code examples
pythoninheritancepytestpython-unittestnose

How can I import a testclass properly to inherit from, without it being run as a test


Context

I have a test class where all my tests inherit from. It cant run by itself as it really doesnt contain any setup info

I wanted to add a test which is executed by ALL tests (adding it to the baseclass seems logical)

But now I notice the basetestclass( => Foo) which I import is being detected as a test itself and runs and is visible in the reports

Code

the base class in base.py

from unittest import TestCase

class Foo(TestCase):
    @classmethod
    def setUpClass(cls):
        # prepare the generic setup stuff based on what is defined in the child class
        print("setupclass Foo done")

    def test_run_in_all_inherited_tests(self):
        print("fooBar")
        assert True

the real test in test_something.py

from base import Foo # <= This is being detected as a testclass object and thus will be executed

class TestFoo(Foo):
    @classmethod
    def setUpClass(cls):
        # define specific test setup
        super().setUpClass()
        print("setup TestFoo done")

    def test_pass(self):
        pass

    def test_assert(self):
        assert False

This triggers a testrun of the imported Foo

pycharm testrun

The Question

How can I import Foo without that its being detected as a 'test' If I remove the test to run in all tests all is fine.

Adding @nottest decorator to Foo wont work since then also all inherited classes are defined nottest.

It needs to run on nose, pytest and unittest testrunners

I noticed if I changed the import statement like below that it also works. But that would mean adjusting a few hundreds of testfiles in different repos. (I'd like to avoid that)

import base
class TestFoo(base.Foo):

Solution

  • The key to the answer seems to be that each test has an attribute __test__ which is set to True when it is a test.

    Setting it to False when the class should not be a test will then let the test collector ignore this class.

    The answer assumes I can only do changes in the base.py

    In python 3.9 classmethod and property decorators can be combined so I wrote a separate answer for that

    answer for < py3.9

    the base class in base.py

    from unittest import TestCase
    
    class MetaFoo(type):
        @property
        def __test__(cls):
            return cls != Foo
    
    class Foo(TestCase, metaclass=MetaFoo):
        @classmethod
        def setUpClass(cls):
            # prepare the generic setup stuff based on what is defined in the child class
            print("setupclass Foo done")
    
        def test_run_in_all_inherited_tests(self):
            print("fooBar")
            assert True
    

    answer for >= py3.9

    the base class in base.py

    from unittest import TestCase
    
    class Foo(TestCase):
        @classmethod
        @property
        def __test__(cls):
            return cls != Foo
    
        @classmethod
        def setUpClass(cls):
            # prepare the generic setup stuff based on what is defined in the child class
            print("setupclass Foo done")
    
        def test_run_in_all_inherited_tests(self):
            print("fooBar")
            assert True
    

    the actual test

    test_something.py

    from base import Foo # <= This will not be detected as a test anymore as __test__ returns False
    
    class TestFoo(Foo):
        @classmethod
        def setUpClass(cls):
            # define specific test setup
            super().setUpClass()
            print("setup TestFoo done")
    
        def test_pass(self):
            pass
    
        def test_assert(self):
            assert False
    

    This doesnt trigger a testrun of the imported Foo anymore pycharm test run