Skip to content

Instantly share code, notes, and snippets.

@fragmuffin
Last active December 11, 2022 15:25
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fragmuffin/a245f59bdcd457936c3b51aa2ebb3f6c to your computer and use it in GitHub Desktop.
Save fragmuffin/a245f59bdcd457936c3b51aa2ebb3f6c to your computer and use it in GitHub Desktop.
python unittest skip by label

This is a demonstration of using a unittest.TestCaseRunner to skip tests by label.

Labels are added with a decorator (by class), then test methods are replaced with a "force skip" wrapper before they're added to a test-suite.

This gist is in response to the stackoverflow question:

https://stackoverflow.com/questions/1068246/python-unittest-how-to-run-only-part-of-a-test-file/48206481#48206481

intalling this gist

To try out this gist,

$ git clone git@gist.github.com:a245f59bdcd457936c3b51aa2ebb3f6c.git
$ cd a245f59bdcd457936c3b51aa2ebb3f6c
$ ./runtests.py --whitelist foo

whitelist

whitelisting labelled tests (skip everything else)

~/temp/a245f59bdcd457936c3b51aa2ebb3f6c (master) $ ./runtests.py --whitelist foo
test_foo (test_things.MyTest2) ... skipped 'label exclusion'
test_bar (test_things.MyTest3) ... skipped 'label exclusion'
test_one (test_things.MyTests1) ... ok
test_two (test_things.MyTests1) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK (skipped=2)

blacklist

blacklisting labelled tests

~/temp/a245f59bdcd457936c3b51aa2ebb3f6c (master) $ ./runtests.py --blacklist foo
test_foo (test_things.MyTest2) ... ok
test_bar (test_things.MyTest3) ... ok
test_one (test_things.MyTests1) ... skipped 'label exclusion'
test_two (test_things.MyTests1) ... skipped 'label exclusion'

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK (skipped=2)
def testlabel(*labels):
"""
Usage::
@testlabel('quick')
class MyTest(unittest.TestCase):
def test_foo(self):
pass
"""
def inner(cls):
# append labels to class
cls._labels = set(labels) | getattr(cls, '_labels', set())
return cls
return inner
#!/usr/bin/env python
import unittest
import functools
import re
class MyTestRunner(unittest.runner.TextTestRunner):
def __init__(self, *args, **kwargs):
"""
Append blacklist & whitelist attributes to TestRunner instance
"""
self.whitelist = set(kwargs.pop('whitelist', []))
self.blacklist = set(kwargs.pop('blacklist', []))
super(MyTestRunner, self).__init__(*args, **kwargs)
@classmethod
def test_iter(cls, suite):
"""
Iterate through test suites, and yield individual tests
"""
for test in suite:
if isinstance(test, unittest.TestSuite):
for t in cls.test_iter(test):
yield t
else:
yield test
def run(self, testlist):
# Change given testlist into a TestSuite
suite = unittest.TestSuite()
# Add each test in testlist, apply skip mechanism if necessary
for test in self.test_iter(testlist):
# Determine if test should be skipped
skip = bool(self.whitelist)
test_method = getattr(test, test._testMethodName)
test_labels = getattr(test, '_labels', set()) | \
getattr(test_method, '_labels', set())
if test_labels & self.whitelist:
skip = False
if test_labels & self.blacklist:
skip = True
if skip:
# Test should be skipped.
# replace original method with function "skip"
# Create a "skip test" wrapper for the actual test method
@functools.wraps(test_method)
def skip_wrapper(*args, **kwargs):
raise unittest.SkipTest('label exclusion')
skip_wrapper.__unittest_skip__ = True
skip_wrapper.__unittest_skip_why__ = 'label exclusion'
setattr(test, test._testMethodName, skip_wrapper)
suite.addTest(test)
# Resume normal TextTestRunner function with the new test suite
super(MyTestRunner, self).run(suite)
if __name__ == '__main__':
import argparse
# ---- create commandline parser
parser = argparse.ArgumentParser(description='Find and run cqparts tests.')
def label_list(value):
return re.split(r'\W+', value)
parser.add_argument('-w', '--whitelist', dest='whitelist',
type=label_list, default=[],
help="list of labels to test (skip all others)")
parser.add_argument('-b', '--blacklist', dest='blacklist',
type=label_list, default=[],
help="list of labels to skip")
args = parser.parse_args()
# ---- Discover and run tests
# Discover Tests
loader = unittest.TestLoader()
tests = loader.discover('.', pattern='test_*.py')
# Run tests
testRunner = MyTestRunner(
blacklist=args.blacklist,
whitelist=args.whitelist,
verbosity=2,
)
testRunner.run(tests)
import unittest
from base import testlabel
@testlabel('quick', 'foo')
class MyTests1(unittest.TestCase):
def test_one(self):
pass
@testlabel('blah')
def test_two(self):
pass
@testlabel('slow')
class MyTest2(unittest.TestCase):
def test_foo(self):
pass
# not labeled
class MyTest3(unittest.TestCase):
def test_bar(self):
pass
@julian-jmc
Copy link

Hi any idea if this could be possible at method level ?

@fragmuffin
Copy link
Author

fragmuffin commented Feb 10, 2020

@julian-jmc : that's a good idea! I've added it in #file-runtests-py-L42

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment