Skip to main content

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​

LinesSymbolRole
26-32SkipTestException raised by skip decorators and skipTest()
34-37_ShouldStopInternal signal to abort a test run
39-42_UnexpectedSuccessRaised when an @expectedFailure test passes
45-83_OutcomeAccumulates success/failure/skip state for one test method
86-100_addSkip, _addErrorModule-level helpers called from testPartExecutor
157-195skip, skipIf, skipUnless, expectedFailurePublic decorators
253-289_AssertRaisesContextContext manager backing assertRaises
371-endTestCaseMain test case class
420-451TestCase.__init__Registers type-equality functions in _type_equality_funcs
494-500setUp, tearDownOverride hooks called before and after each test method
546-575subTestContext manager for parameterized sub-cases
634-697runFull test execution lifecycle
785-816assertRaisesRaises-assertion entry point (call form and context-manager form)
752-783assertTrue, assertFalse, _formatMessageCore 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.