Search code examples
pythoninheritanceabstract-class

How do I avoid redundant python code when implementing 2 identical classes with different inheritance?


I have 2 classes that are identical aside from their inheritance. Each class overrides the same methods. I tried to avoid redundancy by using a common global method, but it wasn't enough to avoid a JSCPD error.

I'm not quite sure how to arrange things so that I only have 1 instance of the over-ridden methods, but each over-riding the methods of different base classes...

Probably if I removed the doc strings (which I've removed below), I'd avoid the linting error, but I'd prefer knowing how to accomplish this correctly.

import time

from django.test import TestCase, TransactionTestCase

LONG_TEST_THRESH_SECS = 20
LONG_TEST_ALERT_STR = f" [ALERT > {LONG_TEST_THRESH_SECS}]"


class TracebaseTestCase(TestCase):

    maxDiff = None
    databases = "__all__"

    def setUp(self):
        self.testStartTime = time.time()

    def tearDown(self):
        _reportRunTime(self.id(), self.testStartTime)

    def setUpClass(self):
        self.classStartTime = time.time()

    def setUpTestData(self):
        _reportRunTime(f"{self.__class__.__name__}.setUpTestData", self.classStartTime)

    class Meta:
        abstract = True


class TracebaseTransactionTestCase(TransactionTestCase):

    maxDiff = None
    databases = "__all__"

    def setUp(self):
        self.testStartTime = time.time()

    def tearDown(self):
        _reportRunTime(self.id(), self.testStartTime)

    def setUpClass(self):
        self.classStartTime = time.time()

    def setUpTestData(self):
        _reportRunTime(f"{self.__class__.__name__}.setUpTestData", self.classStartTime)

    class Meta:
        abstract = True


def _reportRunTime(id, startTime):
    t = time.time() - startTime
    heads_up = ""  # String to include for tests that run too long

    if t > LONG_TEST_THRESH_SECS:
        heads_up = LONG_TEST_ALERT_STR

    print("TEST TIME%s: %s: %.3f" % (heads_up, id, t))

I have a vague recollection back in my C++ school days about creating classes where you could supply a class as input for it's "inheritance" but I don't remember what that was called. I recall it used angle brackets in its declaration. I feel like that's what I need. Something like:

class abstractBaseClass(<base class input>):
    # This would be where the methods would be defined
    # I would also make reportRunTime be a member function here if I knew how to implement this "class template"

class TracebaseTestCase(abstractBaseClass(TestCase))
    pass

class TracebaseTransactionTestCase(abstractBaseClass(TransactionTestCase))
    pass

Am I close?

UPDATE: I fleshed out the rest of my source code, since there seemed to be some question about if it would affect the answer.

The point is that in each case, I am either over-riding the methods of TestCase or TransactionTestCase. And wherever I inherit from (for example) TracebaseTestCase, TestCase is determining when to run setUp, tearDown, setUpClass, and setUpTestData.

The code works as it is. I just want to avoid the jscpd linting error and reduce the redundancy.


Solution

  • You might consider using a mix-in.

    class TestSkeleton:
        maxDiff = None
        databases = "__all__"
    
        def setUp(self):
            self.testStartTime = time.time()
    
        def tearDown(self):
            _reportRunTime(self.id(), self.testStartTime)
    
        def setUpClass(self):
            self.classStartTime = time.time()
    
        def setUpTestData(self):
            _reportRunTime(f"{self.__class__.__name__}.setUpTestData", self.classStartTime)
    
        class Meta:
            abstract = True
    
    
    class TracebaseTestCase(TestSkeleton, TestCase):
        pass
    
    
    class TracebaseTransactionTestCase(TestSkeleton, TransactionTestCase):
        pass
    

    tearDown and setUpTestData probably need some adjustment depending on exactly what _reportRunTime is and what it's first argument is supposed to represent.