Lib/unittest/case.py
cpython 3.14 @ ab2d84fe1023/Lib/unittest/case.py
Lib/unittest/case.py is the largest file in the unittest package. It defines TestCase, the base class every test suite subclasses, along with the full assertion library, skip decorators, subTest, and the internal _Outcome object that accumulates per-method results.
Map
| Lines | Symbol | Role |
|---|---|---|
| 26-32 | SkipTest | Exception raised by skip decorators and skipTest() |
| 34-37 | _ShouldStop | Internal signal to abort a test run |
| 39-42 | _UnexpectedSuccess | Raised when an @expectedFailure test passes |
| 45-83 | _Outcome | Accumulates success/failure/skip state for one test method |
| 86-100 | _addSkip, _addError | Module-level helpers called from testPartExecutor |
| 157-195 | skip, skipIf, skipUnless, expectedFailure | Public decorators |
| 253-289 | _AssertRaisesContext | Context manager backing assertRaises |
| 371-end | TestCase | Main test case class |
| 420-451 | TestCase.__init__ | Registers type-equality functions in _type_equality_funcs |
| 494-500 | setUp, tearDown | Override hooks called before and after each test method |
| 546-575 | subTest | Context manager for parameterized sub-cases |
| 634-697 | run | Full test execution lifecycle |
| 785-816 | assertRaises | Raises-assertion entry point (call form and context-manager form) |
| 752-783 | assertTrue, assertFalse, _formatMessage | Core boolean assertions |
Reading
_Outcome and testPartExecutor
_Outcome is a private accumulator. One instance is created per call to TestCase.run(). The testPartExecutor context manager wraps each phase (setUp, the test method, tearDown) and routes exceptions to the appropriate result.addXxx method.
# CPython: Lib/unittest/case.py:45 _Outcome
class _Outcome(object):
def __init__(self, result=None):
self.expecting_failure = False
self.result = result
self.result_supports_subtests = hasattr(result, "addSubTest")
self.success = True
self.expectedFailure = None
@contextlib.contextmanager
def testPartExecutor(self, test_case, subTest=False):
old_success = self.success
self.success = True
try:
yield
except KeyboardInterrupt:
raise
except SkipTest as e:
self.success = False
_addSkip(self.result, test_case, str(e))
except _ShouldStop:
pass
except:
exc_info = sys.exc_info()
if self.expecting_failure:
self.expectedFailure = exc_info
else:
self.success = False
if subTest:
self.result.addSubTest(test_case.test_case, test_case, exc_info)
else:
_addError(self.result, test_case, exc_info)
exc_info = None
else:
if subTest and self.success:
self.result.addSubTest(test_case.test_case, test_case, None)
finally:
self.success = self.success and old_success
The old_success save-and-restore pattern means that a tearDown failure marks the test failed even if setUp and the method both succeeded. KeyboardInterrupt is re-raised unconditionally so Ctrl-C always aborts the run.
TestCase.run lifecycle
The three phases (setUp, method, tearDown) are each wrapped by testPartExecutor. If setUp fails, the method and tearDown are skipped, but doCleanups always runs. The expecting_failure flag is toggled only around the method call, so a failure in setUp is never silenced as an expected failure.
# CPython: Lib/unittest/case.py:634 TestCase.run
def run(self, result=None):
...
result.startTest(self)
try:
outcome = _Outcome(result)
try:
self._outcome = outcome
with outcome.testPartExecutor(self):
self._callSetUp()
if outcome.success:
outcome.expecting_failure = expecting_failure
with outcome.testPartExecutor(self):
self._callTestMethod(testMethod)
outcome.expecting_failure = False
with outcome.testPartExecutor(self):
self._callTearDown()
self.doCleanups()
if outcome.success:
if expecting_failure:
if outcome.expectedFailure:
self._addExpectedFailure(result, outcome.expectedFailure)
else:
self._addUnexpectedSuccess(result)
else:
result.addSuccess(self)
return result
finally:
outcome.expectedFailure = None
outcome = None
self._outcome = None
finally:
result.stopTest(self)
_callTestMethod was promoted to its own override hook in CPython 3.14 (previously inlined). Async subclasses override just that step to run the coroutine through an event loop.
assertEqual dispatch via _type_equality_funcs
TestCase.__init__ registers type-specific equality functions in a dict keyed by type. assertEqual looks up the types of both operands and, if a specialized function exists, calls it to produce a richer failure message.
# CPython: Lib/unittest/case.py:420 TestCase.__init__
self._type_equality_funcs = {}
self.addTypeEqualityFunc(dict, 'assertDictEqual')
self.addTypeEqualityFunc(list, 'assertListEqual')
self.addTypeEqualityFunc(tuple, 'assertTupleEqual')
self.addTypeEqualityFunc(set, 'assertSetEqual')
self.addTypeEqualityFunc(frozenset, 'assertSetEqual')
self.addTypeEqualityFunc(str, 'assertMultiLineEqual')
The values are method name strings rather than bound methods so that subclasses can override the individual assertXxxEqual methods without re-registering the types.
assertRaises as call form and context manager
assertRaises is overloaded. With a callable argument it calls immediately and checks for the exception. Without a callable it returns an _AssertRaisesContext instance for use in a with block.
# CPython: Lib/unittest/case.py:785 assertRaises
def assertRaises(self, expected_exception, *args, **kwargs):
context = _AssertRaisesContext(expected_exception, self)
try:
return context.handle('assertRaises', args, kwargs)
except:
raise
_AssertRaisesContext.handle inspects args: if a callable was passed it calls it inside testPartExecutor and returns; otherwise it returns self so the caller can use it as a context manager. Both forms set context.exception on success so tests can inspect the caught exception.
setUp, tearDown, and addCleanup
setUp and tearDown are empty hooks that subclasses override. addCleanup pushes callables onto self._cleanups, a stack that doCleanups drains in LIFO order after tearDown regardless of test outcome.
# CPython: Lib/unittest/case.py:494 setUp
def setUp(self):
"Hook method for setting up the test fixture before exercising it."
def tearDown(self):
"Hook method for deconstructing the test fixture after testing it."
gopy notes
Status: not yet ported.
Planned package path: module/unittest/.
_Outcome is the central porting target. A Go equivalent is a struct holding a success flag and a pointer to the current TestResult. The testPartExecutor wrapping maps onto a deferred recovery block, though the subTest nesting (via _subtest pointer and _OrderedChainMap) requires a linked list of sub-test contexts. The __unittest_skip__ attribute protocol is a good candidate for a Go interface method rather than a dynamic attribute check.