CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-unittest-xml-reporting

unittest-based test runner with Ant/JUnit like XML reporting.

Overview
Eval results
Files

result-collection.mddocs/

Result Collection and XML Generation

Comprehensive test result collection system that captures detailed test execution information and generates XML reports compatible with JUnit schema and CI/CD systems.

Capabilities

XMLTestResult Class

Main result collector that extends unittest's TextTestResult to capture test outcomes, timing information, stdout/stderr, and generate XML reports.

class _XMLTestResult(TextTestResult):
    def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1,
                 elapsed_times=True, properties=None, infoclass=None):
        """
        Initialize XML test result collector.
        
        Parameters:
        - stream: file-like object for text output
        - descriptions: int, description verbosity (0=no, 1=short, 2=long)
        - verbosity: int, output verbosity level
        - elapsed_times: bool, track test execution timing
        - properties: dict or None, JUnit testsuite properties
        - infoclass: class or None, custom test info class
        """
    
    def generate_reports(self, test_runner):
        """Generate XML reports using the test runner's configuration."""
    
    def addSuccess(self, test):
        """Record successful test completion."""
    
    def addFailure(self, test, err):
        """Record test failure with exception information."""
    
    def addError(self, test, err):
        """Record test error with exception information."""
    
    def addSkip(self, test, reason):
        """Record skipped test with reason."""
    
    def addSubTest(self, testcase, test, err):
        """Record subtest result (limited support)."""
    
    def addExpectedFailure(self, test, err):
        """Record expected test failure."""
    
    def addUnexpectedSuccess(self, test):
        """Record unexpected test success."""

Usage Examples

Custom Result Class

import xmlrunner
from xmlrunner.result import _XMLTestResult

class CustomTestResult(_XMLTestResult):
    def addSuccess(self, test):
        super().addSuccess(test)
        print(f"✓ {test.id()}")

runner = xmlrunner.XMLTestRunner(
    output='reports',
    resultclass=CustomTestResult
)

JUnit Properties

import unittest
import xmlrunner

# Add custom properties to testsuite
class TestWithProperties(unittest.TestCase):
    def setUp(self):
        # Properties can be set on test suite
        if not hasattr(self.__class__, 'properties'):
            self.__class__.properties = {
                'build_number': '123',
                'environment': 'staging',
                'branch': 'main'
            }
    
    def test_example(self):
        self.assertTrue(True)

unittest.main(testRunner=xmlrunner.XMLTestRunner(output='reports'))

TestInfo Class

Container for detailed test execution information, used internally by _XMLTestResult to track test outcomes and metadata.

class _TestInfo:
    # Test outcome constants
    SUCCESS: int = 0
    FAILURE: int = 1
    ERROR: int = 2
    SKIP: int = 3
    
    def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, 
                 subTest=None, filename=None, lineno=None, doc=None):
        """
        Initialize test information container.
        
        Parameters:
        - test_result: _XMLTestResult instance
        - test_method: test method object
        - outcome: int, test outcome (SUCCESS/FAILURE/ERROR/SKIP)
        - err: tuple or str, exception information
        - subTest: subtest object or None
        - filename: str or None, test file path
        - lineno: int or None, test line number
        - doc: str or None, test method docstring
        """
    
    def test_finished(self):
        """Finalize test information after test completion."""
    
    def get_error_info(self):
        """Get formatted exception information."""
    
    def id(self):
        """Get test identifier."""
    
    # Attributes populated during test execution
    test_result: _XMLTestResult
    outcome: int
    elapsed_time: float
    timestamp: str
    test_name: str
    test_id: str
    test_description: str
    test_exception_name: str
    test_exception_message: str
    test_exception_info: str
    stdout: str
    stderr: str
    filename: str | None
    lineno: int | None
    doc: str | None

Output Capture

The result system captures stdout/stderr during test execution for inclusion in XML reports.

class _DuplicateWriter:
    def __init__(self, first, second):
        """
        Dual-output writer that duplicates output to two streams.
        
        Parameters:
        - first: primary output stream
        - second: secondary stream (typically StringIO for capture)
        """
    
    def write(self, data):
        """Write data to both streams."""
    
    def flush(self):
        """Flush both streams."""
    
    def getvalue(self):
        """Get captured value from secondary stream."""

Usage Example

import io
from xmlrunner.result import _DuplicateWriter

# Capture output while still displaying to console
capture = io.StringIO()
dual_writer = _DuplicateWriter(sys.stdout, capture)

# Use dual_writer for test output
# Later retrieve captured content with capture.getvalue()

XML Report Generation

The result system generates XML reports following JUnit schema with support for multiple output formats.

XML Structure

<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
  <testsuite name="test_module.TestClass" tests="3" failures="1" errors="0" 
             skipped="0" time="0.123" timestamp="2023-12-01T10:30:00">
    <properties>
      <property name="build_number" value="123"/>
    </properties>
    <testcase classname="test_module.TestClass" name="test_success" 
              time="0.001" timestamp="2023-12-01T10:30:00"/>
    <testcase classname="test_module.TestClass" name="test_failure" 
              time="0.002" timestamp="2023-12-01T10:30:01">
      <failure type="AssertionError" message="False is not true">
        <![CDATA[Traceback (most recent call last):
  File "test_module.py", line 10, in test_failure
    self.assertTrue(False)
AssertionError: False is not true]]>
      </failure>
      <system-out><![CDATA[Test output here]]></system-out>
    </testcase>
  </testsuite>
</testsuites>

Constants and Utility Functions

# Format constants for output capture
STDOUT_LINE: str = '\nStdout:\n%s'
STDERR_LINE: str = '\nStderr:\n%s'

def safe_unicode(data, encoding='utf8'):
    """
    Convert data to unicode string with only valid XML characters.
    
    Parameters:
    - data: input data to convert
    - encoding: encoding for byte strings
    
    Returns:
    - str: cleaned unicode string
    """

def testcase_name(test_method):
    """
    Extract test case name from test method.
    
    Parameters:
    - test_method: test method object
    
    Returns:
    - str: test case name in format 'module.TestClass'
    """

def resolve_filename(filename):
    """
    Make filename relative to current directory when possible.
    
    Parameters:
    - filename: str, file path
    
    Returns:
    - str: relative or absolute filename
    """

Integration with Test Discovery

The result system works seamlessly with unittest's test discovery:

import unittest
import xmlrunner

# Test discovery with XML reporting
loader = unittest.TestLoader()
suite = loader.discover('tests', pattern='test_*.py')

runner = xmlrunner.XMLTestRunner(output='test-reports')
result = runner.run(suite)

# Access result information
print(f"Tests run: {result.testsRun}")
print(f"Failures: {len(result.failures)}")
print(f"Errors: {len(result.errors)}")
print(f"Skipped: {len(result.skipped)}")

SubTest Support

Limited support for unittest.TestCase.subTest functionality:

import unittest
import xmlrunner

class TestWithSubTests(unittest.TestCase):
    def test_with_subtests(self):
        for i in range(3):
            with self.subTest(i=i):
                self.assertEqual(i % 2, 0)  # Will fail for i=1

# Note: SubTest granularity may be lost in XML reports
# due to JUnit schema limitations
unittest.main(testRunner=xmlrunner.XMLTestRunner(output='reports'))

Install with Tessl CLI

npx tessl i tessl/pypi-unittest-xml-reporting

docs

core-runner.md

django-integration.md

index.md

jenkins-compatibility.md

result-collection.md

xml-utilities.md

tile.json