Last active
July 19, 2019 10:47
-
-
Save elmotec/c2aa46b1b625774e15bb to your computer and use it in GitHub Desktop.
A convenience test runners for unittest.
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 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