Skip to content

Instantly share code, notes, and snippets.

@catb0t
Last active December 2, 2023 17:56
Show Gist options
  • Save catb0t/304ececa6c55f6e3788d to your computer and use it in GitHub Desktop.
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.
#!/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
Copy link

@dron4ik86 There was a bizarre bug in the code that I fixed.

However, when the calling module of caseFactory differs from caseFactory's own module (test_4 for you), you have to give the keyword argument scope=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

@catb0t
Copy link
Author

catb0t commented Apr 3, 2021

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