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.
» npm install jscpd
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.
@chepner's answer is correct, though I didn't understand why or how it worked because I never understood the concept of mixins. I couldn't map it to my naive notion of multiple inheritance. So I did some testing with trial and error and now have more confidence that I have a better conceptual (though perhaps technically inaccurate) understanding of how they work. I had previously been conceptually dissuaded by the notion of "multiple inheritance" to imply multiple independent parent classes/objects. However (to use a geeky analogy) I now see it more like the parent being "Tuvix" from Star Trek Voyager. The parents (Tuvok and Neelix) are not independent individuals. There's only 1 parent: Tuvix, who is a merging of the 2 parents.
And from my testing, I have come to understand that the order of the superclasses establishes whose characteristics dominate. Precedence goes from left to right. Anything you "override" in the left-side class is what gets set/called when a derived class calls/gets it.
I don't need to reiterate @chepner's answer, but I will provide an example to demonstrate why it works... Take these classes as an example:
class mybaseclass():
"""a.k.a. TestCase"""
classvar = "base"
def member_override_test(self):
print(f"member_override_test in mybaseclass, classvar: [{self.classvar}]")
@classmethod
def classmethod_override_test(cls):
print(f"classmethod_override_test in mybaseclass, classvar: [{cls.classvar}]")
def run_member_super_test(self):
print(f"classvar: {self.classvar}")
print("Calling member_override_test from mybaseclass:")
self.member_override_test()
print("Calling classmethod_override_test from mybaseclass:")
self.classmethod_override_test()
print("Calling run_test from mybaseclass:")
self.run_test()
class mymixinclass():
"""a.k.a. TestSkeleton"""
classvar = "mixin"
def member_override_test(self):
print(f"member_override_test in mymixinclass, classvar: [{self.classvar}]")
@classmethod
def classmethod_override_test(cls):
print(f"classmethod_override_test in mymixinclass, classvar: [{cls.classvar}]")
class mypseudoderivedclass(mymixinclass, mybaseclass):
"""a.k.a. TracebaseTestCase"""
def member_override_test(self):
print(f"member_override_test in mypseudoderivedclass, classvar: [{self.classvar}]")
print(f"Calling super.member_override_test")
super().member_override_test()
def classmethod_override_test(self):
print(f"classmethod_override_test in mypseudoderivedclass, classvar: [{self.classvar}]")
print(f"Calling super.classmethod_override_test")
super().classmethod_override_test()
def run_test(self):
print(f"classvar: {self.classvar}")
print("Calling member_override_test from mypseudoderivedclass object:")
self.member_override_test()
print("Calling classmethod_override_test from mypseudoderivedclass object:")
self.classmethod_override_test()
class reversemypseudoderivedclass(mybaseclass, mymixinclass):
"""a.k.a. TracebaseTestCase - reversing the order of the mixins"""
def member_override_test(self):
print(f"member_override_test in reversemypseudoderivedclass, classvar: [{self.classvar}]")
print(f"Calling super.member_override_test")
super().member_override_test()
def classmethod_override_test(self):
print(f"classmethod_override_test in reversemypseudoderivedclass, classvar: [{self.classvar}]")
print(f"Calling super.classmethod_override_test")
super().classmethod_override_test()
def run_test(self):
print(f"classvar: {self.classvar}")
print("Calling member_override_test from mypseudoderivedclass object:")
self.member_override_test()
print("Calling classmethod_override_test from mypseudoderivedclass object:")
self.classmethod_override_test()
And here's what you see when you play with those classes in the python shell:
In [1]: from DataRepo.tests.tracebase_test_case import mypseudoderivedclass, reversemypseudoderivedclass
...: mpdc = mypseudoderivedclass()
In [2]: mpdc.run_test()
...:
classvar: mixin
Calling member_override_test from mypseudoderivedclass object:
member_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.member_override_test
member_override_test in mymixinclass, classvar: [mixin]
Calling classmethod_override_test from mypseudoderivedclass object:
classmethod_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.classmethod_override_test
classmethod_override_test in mymixinclass, classvar: [mixin]
In [3]: mpdc.member_override_test()
member_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.member_override_test
member_override_test in mymixinclass, classvar: [mixin]
In [4]: mpdc.classmethod_override_test()
classmethod_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.classmethod_override_test
classmethod_override_test in mymixinclass, classvar: [mixin]
In [5]: mpdc.run_member_super_test()
classvar: mixin
Calling member_override_test from mybaseclass:
member_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.member_override_test
member_override_test in mymixinclass, classvar: [mixin]
Calling classmethod_override_test from mybaseclass:
classmethod_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.classmethod_override_test
classmethod_override_test in mymixinclass, classvar: [mixin]
Calling run_test from mybaseclass:
classvar: mixin
Calling member_override_test from mypseudoderivedclass object:
member_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.member_override_test
member_override_test in mymixinclass, classvar: [mixin]
Calling classmethod_override_test from mypseudoderivedclass object:
classmethod_override_test in mypseudoderivedclass, classvar: [mixin]
Calling super.classmethod_override_test
classmethod_override_test in mymixinclass, classvar: [mixin]
In [6]: rmpdc = reversemypseudoderivedclass()
...: rmpdc.run_test()
classvar: base
Calling member_override_test from mypseudoderivedclass object:
member_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.member_override_test
member_override_test in mybaseclass, classvar: [base]
Calling classmethod_override_test from mypseudoderivedclass object:
classmethod_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.classmethod_override_test
classmethod_override_test in mybaseclass, classvar: [base]
In [7]: rmpdc.member_override_test()
member_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.member_override_test
member_override_test in mybaseclass, classvar: [base]
In [8]: rmpdc.classmethod_override_test()
classmethod_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.classmethod_override_test
classmethod_override_test in mybaseclass, classvar: [base]
In [9]: rmpdc.run_member_super_test()
classvar: base
Calling member_override_test from mybaseclass:
member_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.member_override_test
member_override_test in mybaseclass, classvar: [base]
Calling classmethod_override_test from mybaseclass:
classmethod_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.classmethod_override_test
classmethod_override_test in mybaseclass, classvar: [base]
Calling run_test from mybaseclass:
classvar: base
Calling member_override_test from mypseudoderivedclass object:
member_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.member_override_test
member_override_test in mybaseclass, classvar: [base]
Calling classmethod_override_test from mypseudoderivedclass object:
classmethod_override_test in reversemypseudoderivedclass, classvar: [base]
Calling super.classmethod_override_test
classmethod_override_test in mybaseclass, classvar: [base]
Note that the class variable classvar's value and the method handling the (super) calls depend on the order of the mixins in the class's multiple inheritances.
Factory Alternative
Before I understood the mixins, I tried another solution that solves the sample problem in a different way. You can indeed parameterize the base class, which I learned from another answer, by creating a factory function, then vivify the derived classes by making factory calls.
Note, in this answer, I realized I could avoid overriding setUpClass by setting the "class start time" in the member data:
import time
from django.test import TestCase, TransactionTestCase
LONG_TEST_THRESH_SECS = 20
LONG_TEST_ALERT_STR = f" [ALERT > {LONG_TEST_THRESH_SECS}]"
def test_case_class_factory(base_class):
class TracebaseTestCaseTemplate(base_class):
maxDiff = None
databases = "__all__"
classStartTime = time.time()
def setUp(self):
self.testStartTime = time.time()
def tearDown(self):
reportRunTime(self.id(), self.testStartTime)
@classmethod
def setUpTestData(cls):
super().setUpTestData()
reportRunTime(f"{cls.__name__}.setUpTestData", cls.classStartTime)
class Meta:
abstract = True
return TracebaseTestCaseTemplate
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))
# Classes created by the factory with different base classes:
TracebaseTestCase = test_case_class_factory(TestCase)
TracebaseTransactionTestCase = test_case_class_factory(TransactionTestCase)
Both solutions work, and while I like the preserved inheritance of the factory method, once you understand mixins, I feel like @chepner's code is easier to read, so I will select his answer.
» npm install jscpd
» pip install danger-py-jscpd
Copy-paste is one of the most common sources of technical debt, and jscpd is the most language-comprehensive tool I've found for hunting it down.
What it does
Finds duplicated code blocks across your codebase using the Rabin-Karp algorithm. You point it at a directory, it tells you exactly where you (or your teammates) copy-pasted.
npx jscpd ./src
That's it. No install required.
Why it stands out
-
223 supported formats — JS, TS, Python, Go, Rust, Java, C/C++, PHP, Ruby, Vue, Svelte, Astro, Terraform, SQL, Markdown, YAML... even Brainfuck and APL
-
Cross-file detection — a
<script>block in a.vuefile can match a.tsfile -
CI-friendly —
--threshold 5fails the build if duplication exceeds 5% -
Multiple reporters —
html,json,xml,sarif(GitHub Code Scanning),markdown,csv -
AI reporter — compact output with ~79% fewer tokens, designed for piping into LLM prompts
-
MCP server — works as a Model Context Protocol tool for AI assistants
-
Ignore blocks — wrap noisy code with
/* jscpd:ignore-start */comments -
Git blame integration — find out who wrote the duplicated blocks
-
Self-dogfoods — the repo runs jscpd on itself in CI
Sample output (silent mode)
Found 60 exact clones with 3414 (46.81%) duplicated lines in 100 files. Execution Time: 1381ms
Links
-
https://jscpd.dev
-
GitHub: https://github.com/kucherenko/jscpd
-
npm: https://www.npmjs.com/package/jscpd Used in GitHub Super Linter, Mega-Linter, and Codacy. MIT licensed.
» pip install guardrails