Search code examples
pythonunit-testing

How use Python unittest to test a class hierarchy?


given a Python class hierarchy, say

class Base:
    def method1
    def method2
    def method3
    ...
class Derived1(Base)
class Derived2(Base)

etc.

and given that Base defines some common interface which is re-implemented differently in each Derived class. What would be the most straightforward way to unit test all member functions of all classes. Since i'm having a class hierarchy, i thought of doing something like...

class MyTestCase(unittest.TestCase):
    def testMethod1:
        ## Just as an example. Can be similar
        self.failUnless(self.instance.method1())
    def testMethod2
    ...

And then setting up the test suite with test cases for different instantiations of the class hierarchy But how do i get the "instance" parameter into the MyTestCase class?


Solution

  • The skip decorator applies to all of the subclasses as well as the base class so you cannot simply tell unittest to skip the base. The simplest thing is probably to not have the base test class derive from TestCase at all:

    import unittest
    
    class Base: pass
    class Derived1(Base): pass
    
    class BaseTest(object):
        cls = None
    
        def setUp(self):
            self.instance = self.cls()
    
        def test_Method1(self):
            print("run test_Method1, cls={}".format(self.cls))
    
    class TestParent(BaseTest, unittest.TestCase):
        cls = Base
    
    class TestDerived1(BaseTest, unittest.TestCase):
        cls = Derived1
    
    unittest.main()
    

    The equivalent code using pytest would be:

    class Base: pass
    class Derived1(Base): pass
    
    class BaseTest(object):
        cls = None
    
        def __init__(self):
            self.instance = self.cls()
    
        def test_Method1(self):
            print("run test_Method1, cls={}".format(self.cls))
    
    class TestParent(BaseTest):
        cls = Base
    
    class TestDerived1(BaseTest):
        cls = Derived1
    

    pytest by default looks in files with a test_ prefix for functions with a test_ prefix or classes with a Test prefix. In the test classes it looks for methods with the test_ prefix.

    So it should all just work as desired if converted to use pytest.

    Alternatively with pytest you could parametrize test functions with the classes to be tested but that's probably a bigger change. Code like this would run the test once for each class listed in the instance fixture params:

    import pytest
    
    @pytest.fixture(params=[Base, Derived1])
    def instance(request: FixtureRequest) -> Base:
        cls: Base = request.param
        yield cls()
    
    def test_something(instance: Base) -> None:
        assert instance.method() == 42 # or whatever