Getting Python's unittest results in a tearDown() method Getting Python's unittest results in a tearDown() method python python

Getting Python's unittest results in a tearDown() method


This solution works for Python versions 2.7 to 3.9 (the highest current version), without any decorators or other modification in any code before tearDown. Everything works according to the built-in classification of results. Skipped tests or expectedFailure are also recognized correctly. It evaluates the result of the current test, not a summary of all tests passed so far. Compatible also with pytest.

import unittestclass MyTest(unittest.TestCase):    def tearDown(self):        if hasattr(self, '_outcome'):  # Python 3.4+            result = self.defaultTestResult()  # These two methods have no side effects            self._feedErrorsToResult(result, self._outcome.errors)        else:  # Python 3.2 - 3.3 or 3.0 - 3.1 and 2.7            result = getattr(self, '_outcomeForDoCleanups', self._resultForDoCleanups)        error = self.list2reason(result.errors)        failure = self.list2reason(result.failures)        ok = not error and not failure        # Demo:   report short info immediately (not important)        if not ok:            typ, text = ('ERROR', error) if error else ('FAIL', failure)            msg = [x for x in text.split('\n')[1:] if not x.startswith(' ')][0]            print("\n%s: %s\n     %s" % (typ, self.id(), msg))    def list2reason(self, exc_list):        if exc_list and exc_list[-1][0] is self:            return exc_list[-1][1]    # DEMO tests    def test_success(self):        self.assertEqual(1, 1)    def test_fail(self):        self.assertEqual(2, 1)    def test_error(self):        self.assertEqual(1 / 0, 1)

Comments: Only one or zero exceptions (error or failure) need be reported, because not more can be expected before tearDown. The package unittest expects that a second exception can be raised by tearDown. Therefore the lists errors and failures can contain only one or zero elements together before tearDown. Lines after "demo" comment are reporting a short result.

Demo output: (not important)

$ python3.5 -m unittest testEF.ERROR: test.MyTest.test_error     ZeroDivisionError: division by zeroFAIL: test.MyTest.test_fail     AssertionError: 2 != 1==========================================================... skipped usual output from unittest with tracebacks ......Ran 3 tests in 0.002sFAILED (failures=1, errors=1)

Comparison to other solutions - (with respect to the commit history of the Python source repository):

  • This solution uses a private attribute of TestCase instance like manyother solutions,but I checked carefully all relevant commits in the Python source repositorythat three alternative names cover the code history since Python2.7 to 3.6.2 without any gap. It can be a problem after some new majorPython release, but it could be clearly recognized, skipped and easily fixedlater for a new Python. An advantage is that nothing is modified beforerunning tearDown, it should never break the test and all functionality ofunittest is supported, works with pytest and it could work many extending packages, but not with nosetest (not a suprise becase nosetest is not compatible e.g. with unittest.expectedFailure).

  • The solutions with decorators on the user test methods or with a customizedfailureException (mgilson, Pavel Repin 2nd way, kenorb) are robust againstfuture Pythonversions, but if everything should work completely, they would grow like asnow ball with more supported exceptions and more replicated internalsof unittest. The decorated functions have less readable tracebacks(even more levels added by one decorator), they are more complicated fordebugging and it is unpleasant if another more important decoratorhas a problem. (Thanks to mgilson the basic functionality is ready and knownissues can be fixed.)

  • The solution with a modified run method and catched resultparameter

    • (scoffey) should workalso for Python 2.6. The interpretation of results can be improved torequirements of the question, but nothing can work in Python 3.4+,because result is updated after tearDown call, never before.
  • Mark G.: (tested with Python 2.7, 3.2, 3.3, 3.4 and with nosetest)

  • solution by exc_info() (Pavel Repin's second way) works only with Python 2.

  • Other solutions are principally similar, but less complete or with moredisadvantages.


Explained by Python source repository= Lib/unittest/case.py =Python v 2.7 - 3.3

class TestCase(object):    ...    def run(self, result=None):        ...        self._outcomeForDoCleanups = result   # Python 3.2, 3.3        # self._resultForDoCleanups = result  # Python 2.7        #                                     # Python 2.6 - no result saved        ...        try:            testMethod()        except...   # Many times for different exception classes            result.add...(self, sys.exc_info())  # _addSkip, addError, addFailure        ...        try:            self.tearDown()        ...

Python v. 3.4 - 3.6

    def run(self, result=None):        ...        # The outcome is a context manager to catch and collect different exceptions        self._outcome = outcome        ...        with outcome...(self):            testMethod()        ...        with outcome...(self):            self.tearDown()        ...        self._feedErrorsToResult(result, outcome.errors)

Note (by reading Python commit messages): A reason why test results are so much decoupled from tests is memory leaks prevention. Every exception information can access to frames of the failed process state, including all local variables. If a frame is assigned to a local variable in a code block that could also fail, then a cross memory reference could be easily created.

It is not terrible, thanks to the garbage collector, but the free memory can become fragmented more quickly than if the memory would be released correctly. This is a reason why exception information and traceback are converted very soon to strings and why temporary objects like self._outcome are encapsulated and are set to None in a finally block in order to memory leaks are prevented.


If you take a look at the implementation of unittest.TestCase.run, you can see that all test results are collected in the result object (typically a unittest.TestResult instance) passed as argument. No result status is left in the unittest.TestCase object.

So there isn't much you can do in the unittest.TestCase.tearDown method unless you mercilessly break the elegant decoupling of test cases and test results with something like this:

import unittestclass MyTest(unittest.TestCase):    currentResult = None # Holds last result object passed to run method    def setUp(self):        pass    def tearDown(self):        ok = self.currentResult.wasSuccessful()        errors = self.currentResult.errors        failures = self.currentResult.failures        print ' All tests passed so far!' if ok else \                ' %d errors and %d failures so far' % \                (len(errors), len(failures))    def run(self, result=None):        self.currentResult = result # Remember result for use in tearDown        unittest.TestCase.run(self, result) # call superclass run method    def test_onePlusOneEqualsTwo(self):        self.assertTrue(1 + 1 == 2) # Succeeds    def test_onePlusOneEqualsThree(self):        self.assertTrue(1 + 1 == 3) # Fails    def test_onePlusNoneIsNone(self):        self.assertTrue(1 + None is None) # Raises TypeErrorif __name__ == '__main__':    unittest.main()

This works for Python 2.6 - 3.3 (modified for new Python below).


CAVEAT: I have no way of double checking the following theory at the moment, being away from a dev box. So this may be a shot in the dark.

Perhaps you could check the return value of sys.exc_info() inside your tearDown() method, if it returns (None, None, None), you know the test case succeeded. Otherwise, you could use returned tuple to interrogate the exception object.

See sys.exc_info documentation.

Another more explicit approach is to write a method decorator that you could slap onto all your test case methods that require this special handling. This decorator can intercept assertion exceptions and based on that modify some state in self allowing your tearDown method to learn what's up.

@assertion_trackerdef test_foo(self):    # some test logic