unit testing class hierarchies

Steven D'Aprano steve+comp.lang.python at pearwood.info
Tue Oct 2 21:20:52 EDT 2012


On Wed, 03 Oct 2012 08:30:19 +1000, Ben Finney wrote:

> Ulrich Eckhardt <ulrich.eckhardt at dominolaser.com> writes:
> 
>> I want test_base() to be run as part of both TestD1 and TestD2, because
>> it tests basic functions provided by both classes D1 and D2.
> 
> It sounds, from your description so far, that you have identified a
> design flaw in D1 and D2.
> 
> The common functionality should be moved to a common code point (maybe a
> base class of D1 and D2; maybe a function without need of a class). Then
> you'll have only one occurrence of that functionality to test, which is
> good design as well as easier test code :-)

But surely, regardless of where that functionality is defined, you still 
need to test that both D1 and D2 exhibit the correct behaviour? Otherwise 
D2 (say) may break that functionality and your tests won't notice.

Given a class hierarchy like this:

class AbstractBaseClass:
    spam = "spam"

class D1(AbstractBaseClass): pass
class D2(D1): pass


I write tests like this:

class TestD1CommonBehaviour(unittest.TestCase):
    cls = D1
    def testSpam(self):
         self.assertTrue(self.cls.spam == "spam")
    def testHam(self):
         self.assertFalse(hasattr(self.cls, 'ham'))
    
class TestD2CommonBehaviour(TestD1CommonBehaviour):
    cls = D2

class TestD1SpecialBehaviour(unittest.TestCase):
    # D1 specific tests here

class TestD2SpecialBehaviour(unittest.TestCase):
    # D2 specific tests here



If D2 doesn't inherit from D1, but both from AbstractBaseClass, I need to 
do a little more work. First, in the test suite I create a subclass 
specifically for testing the common behaviour, write tests for that, then 
subclass from that:


class MyD(AbstractBaseClass):
    # Defeat the prohibition on instantiating the base class
    pass

class TestCommonBehaviour(unittest.TestCase):
    cls = MyD
    def testSpam(self):
         self.assertTrue(self.cls.spam == "spam")
    def testHam(self):
         self.assertFalse(hasattr(self.cls, 'ham'))

class TestD1CommonBehaviour(unittest.TestCase):
    cls = D1

class TestD2CommonBehaviour(unittest.TestCase):
    cls = D2

D1 and D2 specific tests remain the same.


Either way, each class gets tested for the full set of expected 
functionality.



-- 
Steven



More information about the Python-list mailing list