Last active
December 2, 2023 17:56
-
-
Save catb0t/304ececa6c55f6e3788d to your computer and use it in GitHub Desktop.
Sorting Unittests to run in the order they're written, or any order I like.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import unittest | |
import inspect | |
import re | |
class Test_MyTests(unittest.TestCase): | |
def test_run_me_first(self): pass | |
def test_2nd_run_me(self): pass | |
def test_and_me_last(self): pass | |
class Test_AnotherClass(unittest.TestCase): | |
def test_first(self): pass | |
def test_after_first(self): pass | |
def test_the_real_final_thing(self): pass | |
def _sourceFinder (f): | |
return inspect.findsource(f)[1] | |
def suiteFactory ( | |
*testcases, | |
testSorter = None, | |
suiteMaker = unittest.makeSuite, | |
newTestSuite = unittest.TestSuite ): | |
""" | |
make a test suite from test cases, or generate test suites from test cases. | |
*testcases = TestCase subclasses to work on | |
testSorter = sort tests using this function over sorting by line number | |
suiteMaker = should quack like unittest.makeSuite. | |
newTestSuite = should quack like unittest.TestSuite. | |
""" | |
if testSorter is None: | |
ln = lambda tc, f: getattr(tc, f).__code__.co_firstlineno | |
testSorter = lambda tc, a, b: ln(tc, a) - ln(tc, b) | |
test_suite = newTestSuite() | |
for tc in testcases: | |
test_suite.addTest( | |
suiteMaker( | |
tc, | |
sortUsing=lambda a, b, case=tc: testSorter(case, a, b) | |
) | |
) | |
return test_suite | |
def caseFactory ( | |
scope = globals().copy(), | |
caseSorter = _sourceFinder, | |
caseSuperCls = unittest.TestCase, | |
caseMatches = re.compile("^Test") ): | |
""" | |
get TestCase-y subclasses from frame "scope", filtering name and attribs | |
scope = iterable to use for a frame; preferably a hashable (dictionary). | |
caseMatches = regex to match function names against; blank matches every TestCase subclass | |
caseSuperCls = superclass of test cases; unittest.TestCase by default | |
caseSorter = sort test cases using this function over sorting by line number | |
""" | |
# note that 'name' is the string key of the object in the dictionary | |
return sorted( | |
[ | |
scope[name] | |
for name in scope | |
if re.match(caseMatches, name) and inspect.isclass(scope[name]) | |
and issubclass( | |
scope[name], | |
caseSuperCls | |
) | |
], | |
key=caseSorter | |
) | |
if __name__ == '__main__': | |
cases = suiteFactory(*caseFactory()) | |
runner = unittest.TextTestRunner(verbosity=2) | |
runner.run(cases) |
@dron4ik86 There was a bizarre bug in the code that I fixed.
However, when the calling module of
caseFactory
differs fromcaseFactory
's own module (test_4
for you), you have to give the keyword argumentscope=globals().copy()
.
caseFactory
's default for that argument is its own enclosing global scope, which is its module.The correct version of your code looks like
#! /usr/bin/env python3 import unittest from test_4 import suiteFactory, caseFactory class Test_MyTests(unittest.TestCase): def test_run_me_first(self): print("1") def test_2nd_run_me(self): print("2") def test_and_me_last(self): print("3") if __name__ == '__main__': cases = suiteFactory(*caseFactory( scope=globals().copy() )) runner = unittest.TextTestRunner(verbosity=2) runner.run(cases)and works properly.
Thank you for your answer
I fixed a bug where non-class variables matching the regex in the scope were picked up by the iterator and produced an error on issubclass.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@dron4ik86 There was a bizarre bug in the code that I fixed.
However, when the calling module of
caseFactory
differs fromcaseFactory
's own module (test_4
for you), you have to give the keyword argumentscope=globals().copy()
.caseFactory
's default for that argument is its own enclosing global scope, which is its module.The correct version of your code looks like
and works properly.