Skip to content

Instantly share code, notes, and snippets.

@elmotec
Last active July 19, 2019 10:47
Show Gist options
  • Save elmotec/c2aa46b1b625774e15bb to your computer and use it in GitHub Desktop.
Save elmotec/c2aa46b1b625774e15bb to your computer and use it in GitHub Desktop.
A convenience test runners for unittest.
#!/usr/bin/env python
# vim: set encoding=utf-8
"""
My convenience test runners for unittest.
Place it in your tests folder and use it with python -m <test folder>.
(Requires click, and colorama (if you want colors)).
Allows one to run your tests in your tests folder in a spartain way:
>python -m tests
Ran 3 tests in 0.530s
OK
Or with output that can easily be reused as input:
>python -m tests -v -v
tests.testmodule.TestClass.test_this ... ok
tests.testmodule.TestClass.test_that ... ok
tests.testmodule.TestOtherClass.test_and_that_too ... ok
Ran 3 tests in 0.590s
OK
So that you can copy and paste like this:
>python -m tests -v -v tests.testmodule.TestClass.test_this
tests.testmodule.TestClass.test_this ... ok
Ran 1 tests in 0.290s
OK
"""
__license__ = "MIT"
import unittest
import unittest.main
import sys
try:
import colorama
has_colorama = True
except ImportError:
pass
class TestResult(unittest.TextTestResult):
"""Override a few behaviours I don't care for in unittest."""
def __init__(self, stream, descriptions, verbosity, test_index):
"""Initialize TestResult.
Remove all separators if verbosity is 0.
:param stream: output stream.
:param descriptions: display first line of the docstring.
:param verbosity: verbosiry. 0 is nothing at all, 1 dots, 2 ...
:type verbosity: int
"""
super(TestResult, self).__init__(stream, descriptions, verbosity)
if verbosity == 0:
self.separator2 = None
self.test_index = test_index
def getDescription(self, test):
"""Display test in consistent way with unittest input.
Tests are display as <module>.<class>.test_<mehod> so that one can copy
and paste the test as argument of the test as in:
```python
python -m tests mymodule.myclass.test_method
```
:param test: test class.
:type test: unittest.TestCase
:rtype: str
"""
cls = test.__class__
desc = '%s.%s.%s' % (cls.__module__, cls.__qualname__,
test._testMethodName)
if self.descriptions:
doc_first_line = test.shortDescription()
if doc_first_line:
desc += '\n' + doc_first_line
return desc
def startTest(self, test):
unittest.result.TestResult.startTest(self, test) # skip parent.
if not self.showAll:
return
description = self.getDescription(test)
line_length = len(description.split('\n')[-1])
padding = 68 - line_length
if padding < 0:
description = description[-padding:]
padding = 0
index = self.test_index[test.id()]
self.stream.write("%4d. %s" % (index, description))
self.stream.write(padding * '.' + ' ')
self.stream.flush()
class ColoramaTestResult(TestResult):
"""Same as TestResult with colors!"""
reset = colorama.Style.RESET_ALL
success_style = colorama.Fore.GREEN + colorama.Style.BRIGHT
error_style = colorama.Fore.RED + colorama.Style.BRIGHT
failure_style = colorama.Fore.RED + colorama.Style.BRIGHT
skipped_style = reset
expected_failure_style = colorama.Fore.GREEN
unexpected_success_style = colorama.Fore.RED + colorama.Style.BRIGHT
def __init__(self, stream, descriptions, verbosity, test_index):
"""Initialize ColoramaTestResult."""
super(ColoramaTestResult, self).__init__(stream, descriptions,
verbosity, test_index)
def addSuccess(self, test):
"""Set the foreground green."""
self.stream.write(self.success_style)
super(ColoramaTestResult, self).addSuccess(test)
self.stream.write(self.reset)
def addError(self, test, err):
"""Set the foreground red."""
self.stream.write(self.error_style)
super(ColoramaTestResult, self).addError(test, err)
self.stream.write(self.reset)
def addFailure(self, test, err):
"""Set the foreground red."""
self.stream.write(self.failure_style)
super(ColoramaTestResult, self).addFailure(test, err)
self.stream.write(self.reset)
def addSkip(self, test, reason):
"""Set the colors for skipped test."""
self.stream.write(self.skipped_style)
super(ColoramaTestResult, self).addSkip(test, reason)
self.stream.write(self.reset)
def addExpectedFailure(self, test, err):
"""Set the foreground to green."""
self.stream.write(self.expected_failure_style)
super(ColoramaTestResult, self).addExpectedFailure(test, err)
self.stream.write(self.reset)
def addUnexpectedSuccess(self, test, err):
"""Set the foreground to red."""
self.stream.write(self.unexpected_success_style)
super(ColoramaTestResult, self).addUnexpectedSuccess(test, err)
self.stream.write(self.reset)
def iterate_tests(test_suite_or_case):
"""Iterate through all of the test cases in 'test_suite_or_case'.
Credit: https://github.com/testing-cabal/testtools
"""
try:
suite = iter(test_suite_or_case)
except TypeError:
yield test_suite_or_case
else:
for test in suite:
for subtest in iterate_tests(test):
yield subtest
class TestRunner(unittest.TextTestRunner):
"""Override a few of the behaviours I don't care for in unittest."""
def __init__(self, stream=None, descriptions=True, verbosity=1,
failfast=False, buffer=False, resultclass=None,
warnings=None):
"""Initialize the TestRunner.
Do not show the test descriptions.
"""
self.test_index = {}
super(TestRunner, self).__init__(stream, False, verbosity,
failfast, buffer, resultclass,
warnings)
def _makeResult(self):
"""Make a resultclass object to collect results.
Make the results in color if possible.
:rtype: ColoramaTestResult if possible otherwise TestResult
"""
params = (self.stream, self.descriptions, self.verbosity)
if has_colorama:
return ColoramaTestResult(*params, self.test_index)
return TestResult(*params, self.test_index)
def run(self, tests):
"""Run the test suite."""
for index, test in enumerate(iterate_tests(tests)):
test_id = test.id()
if test_id not in self.test_index:
self.test_index[test_id] = index + 1
return super(TestRunner, self).run(tests)
def main(argv):
"""Customized test runner for estimate
Piggy back on unittest.main for the loader and the argument parsing.
Pass a TestRunner as the testrunner for further customizations.
Set warnings to '' because None causes unittest.TextTestResult to reset
it to 'default' which I don't like because it seems to reset filters.
module is set to None to get the same behaviour as python -m unittest.
"""
if has_colorama:
colorama.init()
unittest.main(argv=argv, testRunner=TestRunner, warnings='', module=None)
if __name__ == '__main__':
main(sys.argv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment