unittest.py
Overview
The [unittest.py](/projects/286/67465) module provides pytest integration for tests written using Python’s standard library **unittest** framework. It enables pytest to **discover**, **collect**, **execute**, and **report** `unittest.TestCase`-style tests seamlessly alongside native pytest tests.
Key functionalities include:
Detecting and collecting concrete subclasses of
unittest.TestCase.Wrapping unittest test methods as pytest test items.
Mapping unittest lifecycle setup/teardown methods (
setUpClass,tearDownClass,setup_method,teardown_method) into pytest fixtures.Running unittest test methods with support for asynchronous tests and enhanced debugging.
Translating unittest result callbacks (
addError,addFailure,addSkip, etc.) into pytest outcomes and rich exception info.Special compatibility and exception handling for Twisted Trial’s unittest variant.
Registering pytest as a test reporter when Twisted is detected.
This module is a crucial part of pytest’s **Integration with Other Test Frameworks**, allowing users to run legacy or mixed unittest tests in pytest with minimal friction while benefiting from pytest’s advanced features like fixtures, reporting, and debugging.
Classes and Functions
pytest_pycollect_makeitem(collector: Module | Class, name: str, obj: object) -> UnitTestCase | None
**Purpose:** Pytest collection hook to identify unittest test classes for collection.
**Parameters:**
collector: The pytest collector object (module or class) currently collecting.name: Name of the object being inspected.obj: The actual Python object (class, function, etc.).
**Returns:**
A
UnitTestCasecollector instance ifobjis a concrete subclass ofunittest.TestCase.Noneotherwise.
**Usage:** Called by pytest during collection to discover classes derived from `unittest.TestCase` that are not abstract and not explicitly skipped.
class UnitTestCase(Class)
A pytest collector representing a unittest test class. Inherits from pytest’s `Class`.
**Attributes:**
nofuncargs: Set toTrueto indicate unittest tests do not support pytest funcargs (fixtures) in the traditional way.
**Key Methods:**
newinstance(self) -> unittest.TestCase
Creates a new instance of the unittest test case class, passing "runTest" as the test method name (a default recognized by unittest).collect(self) -> Iterable[Item | Collector]
Collects unittest test methods usingunittest.TestLoaderand yieldsTestCaseFunctionpytest items.
Also registers autouse fixtures to integrate unittest’ssetUpClass/tearDownClassandsetup_method/teardown_methodmethods as pytest fixtures._register_unittest_setup_class_fixture(self, cls: type) -> None
Registers a class-scoped autouse fixture to runsetUpClassandtearDownClassmethods, including handling teardown exceptions and class cleanups._register_unittest_setup_method_fixture(self, cls: type) -> None
Registers a function-scoped autouse fixture to runsetup_methodandteardown_methodmethods around each test method.
**Usage Example:**
# During collection, pytest calls:
unit_test_case = UnitTestCase.from_parent(parent_collector, name="MyTestCase", obj=MyTestCase)
for test_item in unit_test_case.collect():
# test_item is a TestCaseFunction representing a test method
...
class TestCaseFunction(Function)
Represents an individual unittest test method wrapped as a pytest `Function` test item.
**Attributes:**
nofuncargs: Set toTrue(no pytest funcargs)._excinfo: Optional list of pytest ExceptionInfo objects representing captured exceptions.
**Key Methods:**
_getinstance(self) -> unittest.TestCase
Creates an instance of the unittest test case with the current test method name.setup(self) -> None
Prepares the test item before execution, initializes explicit teardown hook.teardown(self) -> None
Runs explicit teardown if set, cleans up references.startTest(self, testcase: unittest.TestCase) -> None
Called at the start of a unittest test run (no-op here).addError(self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType) -> None
Called by unittest on errors; converts exceptions to pytest exception info.addFailure(self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType) -> None
Called on test failures; records failure info.addSkip(self, testcase: unittest.TestCase, reason: str) -> None
Called on skipped tests; raises pytest skip exception.addExpectedFailure(self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType, reason: str = "") -> None
Called on expected failures; raises pytest xfail.addUnexpectedSuccess(self, testcase: unittest.TestCase, reason: twisted.trial.unittest.Todo | None = None) -> None
Called on unexpected success; marks test as failed.runtest(self) -> None
Executes the unittest test method, handles async tests, integrates with pytest tracing and debugging, and manages deferred teardown when--pdbis used._traceback_filter(self, excinfo) -> Traceback
Filters unittest internal frames from tracebacks for cleaner reporting.
**Usage Example:**
test_func = TestCaseFunction.from_parent(unit_test_case, name="test_example")
test_func.runtest() # Runs the unittest test method
@hookimpl(tryfirst=True)
pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None
Pytest hook to customize test report creation.
For
TestCaseFunctionitems, attaches stored unittest exception info (_excinfo) to the pytestcallreport to ensure proper reporting.
def _is_skipped(obj) -> bool
Helper function to check if a unittest test class or instance has been marked with `@unittest.skip`.
Returns `True` if the `__unittest_skip__` attribute is set on the object.
def pytest_configure() -> None
Pytest hook called on configuration stage.
If Twisted Trial is detected, registers
TestCaseFunctionas anIReporterimplementation to integrate with Twisted’s reporting.
class TwistedVersion(Enum)
Enumeration of Twisted framework versions for conditional compatibility logic.
Version24: Twisted version 24 or prior.Version25: Twisted version 25 or later.NotInstalled: Twisted is not installed.
def _get_twisted_version() -> TwistedVersion
Determines the installed Twisted version by inspecting `sys.modules` and querying package metadata.
Returns one of the `TwistedVersion` enum values.
@hookimpl(wrapper=True)
pytest_runtest_protocol(item: Item) -> Iterator[None]
A pytest hook wrapping the test run protocol.
For Twisted version 24, monkeypatches
twisted.python.failure.Failure.__init__to store raw exception info for better exception handling.Yields control to pytest’s normal test running.
def _handle_twisted_exc_info(rawexcinfo: _SysExcInfoType | BaseException) -> _SysExcInfoType
Converts Twisted Trial exception representations (`Failure` instances) into standard Python `sys.exc_info()` tuples for pytest compatibility.
Handles different Twisted versions and removes stored attributes to avoid memory leaks.
Implementation Details and Algorithms
Unittest Test Discovery:
Usespytest_pycollect_makeitemhook to identifyunittest.TestCasesubclasses, skipping abstract classes and those marked with__test__ = False.Test Method Loading:
Usesunittest.TestLoader.getTestCaseNames()to list all test methods in a unittest class, filtering out those marked__test__ = False.Fixture Integration:
Registers autouse pytest fixtures wrappingsetUpClass/tearDownClassandsetup_method/teardown_methodto match unittest lifecycle hooks. These fixtures handle exceptions carefully, including use ofExceptionGroup(Python 3.11+) to aggregate multiple teardown errors.Test Execution and Debugging:
TheTestCaseFunction.runtestmethod runs the unittest test method by instantiating the test case and invoking it with a pytest result adapter. It supports asynchronous test methods and postpones teardown if running under--pdbfor improved debugging.Exception Conversion:
Unittest test result callbacks (addError,addFailure, etc.) are overridden to convert exceptions into pytest’sExceptionInfoobjects and raise appropriate pytest exceptions (skip.Exception,fail.Exception,xfail.Exception).Twisted Trial Support:
Special handling for Twisted’sFailureobjects and trial unittest extensions is implemented. The module monkeypatches Twisted internals to extract raw exception info and registers pytest as a test reporter in Trial environments.
Interaction with Other System Components
pytest Fixture Manager:
Registers unittest setup/teardown methods as autouse fixtures to integrate with pytest’s fixture lifecycle.pytest Hook System:
Usespytest_pycollect_makeitemto collect tests,pytest_runtest_makereportto attach exception info, andpytest_runtest_protocolto monkeypatch Twisted internals.pytest Outcomes:
Converts unittest test outcomes into pytest outcomes (skip,fail,xfail), enabling unified reporting and control flow.Twisted Trial Integration:
Interfaces with Twisted Trial’s unittest framework, adapting exception handling and registering pytest as a reporter when Trial tests are run.Python’s unittest Framework:
Relies onunittest.TestLoaderfor test method discovery and invokes unittest test cases normally during execution.
Visual Diagram: Class Structure and Key Methods
classDiagram
class UnitTestCase {
+nofuncargs: bool = True
+newinstance()
+collect() Iterable[Item | Collector]
-_register_unittest_setup_class_fixture(cls: type)
-_register_unittest_setup_method_fixture(cls: type)
}
class TestCaseFunction {
+nofuncargs: bool = True
-_excinfo: list[ExceptionInfo] | None
+_getinstance()
+setup()
+teardown()
+startTest(testcase)
+addError(testcase, rawexcinfo)
+addFailure(testcase, rawexcinfo)
+addSkip(testcase, reason)
+addExpectedFailure(testcase, rawexcinfo, reason="")
+addUnexpectedSuccess(testcase, reason=None)
+addSuccess(testcase)
+stopTest(testcase)
+addDuration(testcase, elapsed)
+runtest()
-_traceback_filter(excinfo)
}
UnitTestCase <|-- TestCaseFunction
UnitTestCase ..> unittest.TestCase : wraps
Example Usage Snippet
# pytest invokes this hook during collection
def pytest_pycollect_makeitem(collector, name, obj):
try:
ut = sys.modules["unittest"]
if not issubclass(obj, ut.TestCase):
return None
except Exception:
return None
if inspect.isabstract(obj):
return None
return UnitTestCase.from_parent(collector, name=name, obj=obj)
# UnitTestCase collects test methods
class UnitTestCase(Class):
def collect(self):
from unittest import TestLoader
loader = TestLoader()
for name in loader.getTestCaseNames(self.obj):
yield TestCaseFunction.from_parent(self, name=name)
Summary
The [unittest.py](/projects/286/67465) module is a core component of pytest’s support for running unittest-based tests. It bridges the gap between the unittest and pytest worlds by:
Discovering unittest test classes and methods.
Wrapping unittest lifecycle hooks as pytest fixtures.
Executing unittest tests with support for asynchronous code and enhanced debugging.
Translating unittest test outcomes into pytest’s reporting and control framework.
Providing special compatibility with Twisted Trial’s unittest variant.
This integration empowers users to run legacy unittest tests alongside pytest tests, leveraging pytest’s powerful features without rewriting existing test code.