Skip to content

Instantly share code, notes, and snippets.

@hynekcer
Last active March 13, 2025 12:03
Show Gist options
  • Select an option

  • Save hynekcer/1b0a260ef72dae05fe9611904d7b9675 to your computer and use it in GitHub Desktop.

Select an option

Save hynekcer/1b0a260ef72dae05fe9611904d7b9675 to your computer and use it in GitHub Desktop.
Getting Python's unittest results in a tearDown() method (my SO answer, question 4414234)
"""Getting Python's unittest results in a tearDown() method
https://stackoverflow.com/questions/4414234/getting-pythons-unittest-results-in-a-teardown-method#39606065
"""
import unittest
class MyTest(unittest.TestCase):
def tearDown(self):
if hasattr(self._outcome, 'errors'):
# Python 3.4 - 3.10 (These two methods have no side effects)
result = self.defaultTestResult()
self._feedErrorsToResult(result, self._outcome.errors)
else:
# Python 3.11+
result = self._outcome.result
ok = all(test != self for test, text in result.errors + result.failures)
# Demo output: (print short info immediately - not important)
if ok:
print('\nOK: %s' % (self.id(),))
for typ, errors in (('ERROR', result.errors), ('FAIL', result.failures)):
for test, text in errors:
if test is self:
# the full traceback is in the variable `text`
msg = [x for x in text.split('\n')[1:]
if not x.startswith(' ')][0]
print("\n\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)
@unittest.expectedFailure
def test_expected_fail(self):
self.assertEqual(2, 1)
@unittest.expectedFailure
def test_expected_error(self):
self.assertEqual(1 / 0, 1)
@unittest.expectedFailure
def test_unexpected_success(self):
self.assertEqual(1, 1)
@fruch

fruch commented Jul 24, 2019

Copy link
Copy Markdown

@hynekcer this was very helpful

I hope you don't mind me borrowing this 💎

I've changed a bit with extra verbosity, since i'm doing a system test, i.e. I'm running one test at a time, and my tearDowns are too long to get the answers I need:

        if not ok:
            typ, text = ('ERROR', error) if error else ('FAIL', failure)
            TEST_LOG.error("="*70)
            TEST_LOG.error("%s: %s" % (typ, self.id()))
            TEST_LOG.error("-" * 70)
            TEST_LOG.error(text)
            TEST_LOG.error("-" * 70)

@hynekcer

Copy link
Copy Markdown
Author

Yes I agree with every use of this snippet or my answer on Stackoverflow. This solution can be even extended to extract a traceback (are you interested?), but it was not was not a subject of the question and a short clear code is usually preferred on SO.

@fruch

fruch commented Jul 25, 2019

Copy link
Copy Markdown

Thanks, mentioned my extra verbosity here and not in SO, for the exact reason, whom ever find this (since it's not linked from SO) might be interested :)

the one downside of this change, it's make my argument of switch to pytest right now a bit less strong 😞
(Since I know how to get this working in pytest, with a few lines in conftest.py)

@hynekcer

hynekcer commented Mar 5, 2022

Copy link
Copy Markdown
Author

Updated for Python 3.11

@SissiWei123

SissiWei123 commented Jun 16, 2022

Copy link
Copy Markdown

Hi, I am really interested in extending this to extract a traceback when a test fails

@hynekcer

Copy link
Copy Markdown
Author

@SissiWei123 It is easy: The full traceback is in the variable text. I added it as a comment to the code

@johnthacker

Copy link
Copy Markdown

Note that the Python 3.11 approach doesn't work if the tests are run with pytest and the pytest developers have no no intention of making it work, since the _Outcome is a private attribute. The 3.4 to 3.10 code works fine when pytest is used to run unittest based tests.

@fruch

fruch commented Jan 6, 2023

Copy link
Copy Markdown

Note that the Python 3.11 approach doesn't work if the tests are run with pytest and the pytest developers have no no intention of making it work, since the _Outcome is a private attribute. The 3.4 to 3.10 code works fine when pytest is used to run unittest based tests.

Why do you need this trickery when using pytest as a runner ? pytest offer much better alternatives to this hack

@johnthacker

Copy link
Copy Markdown

Why do you need this trickery when using pytest as a runner ? pytest offer much better alternatives to this hack

We don't need it, but with Python 3.4-3.10 the original hack works the same both with and without using pytest.

@yzm157

yzm157 commented May 11, 2023

Copy link
Copy Markdown

In this way, the results of pass, fail, and error can be obtained, but it is impossible to know whether the use case is skipped. is there any way to know

@sam-ghosh

Copy link
Copy Markdown

Are you sure this works because I am getting self._outcome.result.error - result does not have error attr or failure attr
image

@hynekcer

Copy link
Copy Markdown
Author

You can see the comments that one branch is used by Python <= 3.10 and the other by Python >= 3.11.
The used branch works and the other is invalid. You probably evaluate it only in Python <= 3.10 but you should also try it in Python >= 3.11 to can verify the code.

@sam-ghosh

sam-ghosh commented Jun 26, 2024

Copy link
Copy Markdown

I checked again, and the attribute "error" cannot be found occurs only when using pytest (7.4.4 is my pytest version, also checked for pytest 8.2), your code works with python - m unittest test.py . Any way to fix this for pytest?

image

@hynekcer

hynekcer commented Jul 1, 2024

Copy link
Copy Markdown
Author

It works also with pytest 7.4.4 or 8.2.2 (your versions) if Python is <= 3.10.x

The package "pytest " is so different from "unittest" that I don't have the energy to start reverse engineering to fix it for fun for Python >= 3.11.

@fluffy-kaiju

Copy link
Copy Markdown

Thanks! Work perfectly for me 😄
Python 3.11.9

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