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

xml-utilities.mddocs/

XML Document Construction

Low-level utilities for building XML test reports with proper JUnit schema compliance. Provides context management, counter tracking, and CDATA section handling for generating well-formed XML documents.

Capabilities

TestXMLBuilder Class

Main XML document builder that encapsulates rules for creating JUnit-compatible XML test reports with proper hierarchy and formatting.

class TestXMLBuilder:
    def __init__(self):
        """Initialize XML document builder with empty document."""
    
    def begin_context(self, tag, name):
        """
        Begin new XML context (testsuites, testsuite, or testcase).
        
        Parameters:
        - tag: str, XML tag name
        - name: str, context name attribute
        """
    
    def end_context(self):
        """
        End current context and append to parent.
        
        Returns:
        - bool: True if context was ended, False if no context
        """
    
    def current_context(self):
        """
        Get current XML context.
        
        Returns:
        - TestXMLContext: current context or None
        """
    
    def context_tag(self):
        """
        Get tag name of current context.
        
        Returns:
        - str: current context tag name
        """
    
    def append(self, tag, content, **kwargs):
        """
        Append XML element with attributes to current context.
        
        Parameters:
        - tag: str, XML tag name
        - content: str, element content
        - **kwargs: element attributes
        
        Returns:
        - Element: created XML element
        """
    
    def append_cdata_section(self, tag, content):
        """
        Append element with CDATA content to current context.
        
        Parameters:
        - tag: str, XML tag name
        - content: str, CDATA content
        
        Returns:
        - Element: created XML element
        """
    
    def increment_counter(self, counter_name):
        """
        Increment counter in current context and all parents.
        
        Parameters:
        - counter_name: str, counter name ('tests', 'errors', 'failures', 'skipped')
        """
    
    def finish(self):
        """
        End all open contexts and return formatted XML document.
        
        Returns:
        - bytes: pretty-printed XML document
        """

Usage Examples

Basic XML Report Construction

from xmlrunner.builder import TestXMLBuilder

builder = TestXMLBuilder()

# Create testsuites root
builder.begin_context('testsuites', 'all_tests')

# Create testsuite
builder.begin_context('testsuite', 'test_module.TestClass')

# Add testcase
builder.begin_context('testcase', 'test_example')
builder.append('success', '', message='Test passed')
builder.increment_counter('tests')
builder.end_context()  # End testcase

builder.end_context()  # End testsuite
builder.end_context()  # End testsuites

# Generate XML
xml_content = builder.finish()
print(xml_content.decode('utf-8'))

Error Reporting with CDATA

builder = TestXMLBuilder()
builder.begin_context('testsuites', 'all_tests')
builder.begin_context('testsuite', 'test_module.TestClass')
builder.begin_context('testcase', 'test_failure')

# Add failure with traceback
failure_info = """Traceback (most recent call last):
  File "test.py", line 10, in test_failure
    self.assertTrue(False)
AssertionError: False is not true"""

builder.append('failure', '', type='AssertionError', message='False is not true')
builder.append_cdata_section('failure', failure_info)

builder.increment_counter('tests')
builder.increment_counter('failures')

xml_content = builder.finish()

TestXMLContext Class

Represents XML document hierarchy context with automatic counter tracking and time measurement.

class TestXMLContext:
    def __init__(self, xml_doc, parent_context=None):
        """
        Initialize XML context.
        
        Parameters:
        - xml_doc: Document, XML document instance
        - parent_context: TestXMLContext or None, parent context
        """
    
    def begin(self, tag, name):
        """
        Begin context by creating XML element.
        
        Parameters:
        - tag: str, XML tag name
        - name: str, name attribute value
        """
    
    def end(self):
        """
        End context and set timing/counter attributes.
        
        Returns:
        - Element: completed XML element
        """
    
    def element_tag(self):
        """
        Get tag name of this context's element.
        
        Returns:
        - str: tag name
        """
    
    def increment_counter(self, counter_name):
        """
        Increment counter if valid for this context type.
        
        Parameters:
        - counter_name: str, counter name
        """
    
    def elapsed_time(self):
        """
        Get formatted elapsed time for this context.
        
        Returns:
        - str: elapsed time in seconds (3 decimal places)
        """
    
    def timestamp(self):
        """
        Get ISO-8601 formatted timestamp for context end.
        
        Returns:
        - str: timestamp string
        """
    
    # Attributes
    xml_doc: Document
    parent: TestXMLContext | None
    element: Element
    counters: dict[str, int]

Counter Rules

Different XML elements support different counters:

  • testsuites: supports tests, errors, failures counters
  • testsuite: supports tests, errors, failures, skipped counters
  • testcase: no counters (individual test level)

Text Processing Utilities

Functions for cleaning and processing text content for XML compatibility.

def replace_nontext(text, replacement='\uFFFD'):
    """
    Replace invalid XML characters in text.
    
    Parameters:
    - text: str, input text
    - replacement: str, replacement character
    
    Returns:
    - str: cleaned text with invalid XML characters replaced
    """

# Constants
UTF8: str = 'UTF-8'  # Default encoding for XML documents
INVALID_XML_1_0_UNICODE_RE: Pattern  # Regex for invalid XML 1.0 characters

Usage Examples

Text Cleaning

from xmlrunner.builder import replace_nontext

# Clean problematic characters from test output
raw_output = "Test output with \x00 null character"
clean_output = replace_nontext(raw_output)
# Result: "Test output with � null character"

# Use in XML building
builder.append_cdata_section('system-out', clean_output)

CDATA Section Handling

The builder properly handles CDATA sections, including splitting content that contains the CDATA end marker.

# Content with CDATA end marker is automatically split
problematic_content = "Some content ]]> with end marker ]]> inside"
builder.append_cdata_section('system-out', problematic_content)

# Results in multiple CDATA sections:
# <system-out><![CDATA[Some content ]]]]><![CDATA[> with end marker ]]]]><![CDATA[> inside]]></system-out>

XML Schema Compliance

The builder generates XML that complies with JUnit schema requirements:

  • Proper element hierarchy (testsuites → testsuite → testcase)
  • Required attributes (name, tests, time, timestamp)
  • Valid counter attributes based on element type
  • Proper CDATA escaping for content with special characters
  • UTF-8 encoding with XML declaration

Performance Considerations

The XML builder is optimized for typical test report sizes:

  • Uses DOM for structured building (suitable for reports with thousands of tests)
  • Incremental counter updates avoid full tree traversal
  • CDATA splitting handles edge cases without performance impact
  • Memory usage scales linearly with test count

Integration with Result System

The builder is used internally by _XMLTestResult but can be used independently:

from xmlrunner.builder import TestXMLBuilder
from xml.dom.minidom import Document

# Direct usage for custom XML generation
builder = TestXMLBuilder()

# Build custom structure
builder.begin_context('testsuites', 'custom_run')
builder.begin_context('testsuite', 'my_tests')

for test_name, result in test_results.items():
    builder.begin_context('testcase', test_name)
    if result['passed']:
        builder.increment_counter('tests')
    else:
        builder.append('failure', '', type='CustomError', message=result['error'])
        builder.increment_counter('tests')
        builder.increment_counter('failures')
    builder.end_context()

xml_bytes = builder.finish()

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