xUnit reporting
The esptest.testcase package produces JUnit/xUnit-style XML reports that CI
systems (GitLab, Jenkins, …) understand. There are two ways to build a
report:
XunitLogger— record cases incrementally while a test run executes.Result dataclasses — build the full result tree and serialize it in one shot (handy for converting another framework’s results).
Recording incrementally with XunitLogger
XunitLogger flushes the XML to disk after each case (and periodically while a
case streams output), so a partial report survives even if the runner crashes
mid-test.
from esptest.testcase.xunit import XunitLogger
logger = XunitLogger('./xunit_report', suite_name='wifi-suite')
logger.set_config({'package': 'esp-test-utils', 'file': 'test_wifi.py'})
# passing case
logger.begin_case('test_connect', classname='wifi.station')
logger.add_sys_out('connecting to AP ...')
logger.end_case()
# failing case
logger.begin_case('test_disconnect', classname='wifi.station')
logger.add_sys_err('serial closed unexpectedly')
logger.end_case(result=False, message='disconnect timeout', failure_type='timeout')
# skipped case
logger.begin_case('test_wpa3', classname='wifi.station')
logger.add_skipped('target does not support WPA3')
logger.end_case()
report_path = logger.flush(force=True)
end_case(result=False, ...) marks the running case FAILED; add_skipped
marks it SKIPPED.
Attaching performance details
A case can carry structured performance data via ResultDetail. Persist it to
JSON next to the report using a path relative to the report directory; the
relative path is recorded on the case so it is auto-loaded when the report is
parsed back.
from pathlib import Path
from esptest.testcase.result import ResultDetail
logger.begin_case('test_tcp_tx_throughput', classname='iperf.tcp')
detail_rel = 'result_details/test_tcp_tx_throughput.json'
detail = logger.running_case.add_result_detail(
ResultDetail(
type='throughput',
context='iperf tcp tx',
params={'proto': 'tcp', 'direction': 'tx'},
result={'throughput_mbps': 94.2, 'unit': 'Mbits/sec'},
brief_message='tcp tx 94.2 Mbits/sec',
),
file_name=detail_rel,
)
detail.save_json(Path('./xunit_report') / detail_rel)
logger.end_case()
Building from dataclasses
When the full set of results is already known, assemble the tree and serialize it directly:
from esptest.testcase.result import (
TestCaseResult,
TestCaseStatus,
TestSuiteResult,
TestSuitesResult,
)
from esptest.testcase.xunit import generate_xunit_xml, save_xunit_xml
suites = TestSuitesResult(
name='esp-test-utils',
test_suites=[
TestSuiteResult(
name='iperf',
test_cases=[
TestCaseResult(name='test_tcp_tx', classname='iperf.tcp', duration=60.0),
TestCaseResult(
name='test_tcp_rx',
classname='iperf.tcp',
status=TestCaseStatus.FAILED,
message='throughput too low',
failure_type='performance',
),
],
)
],
)
xml_text = generate_xunit_xml(suites) # XML as a string
save_xunit_xml(suites, './xunit_report/iperf_result.xml') # write to disk
Parsing an existing report
parse_xunit_xml reads an XML report back into the result dataclasses,
auto-loading any referenced ResultDetail JSON files:
from esptest.testcase.xunit import parse_xunit_xml
suites = parse_xunit_xml('./xunit_report/iperf_result.xml')
print(f'tests={suites.tests}, failures={suites.failures}, errors={suites.errors}')
for suite in suites.test_suites:
for case in suite.test_cases:
print(f'[{case.status}] {suite.name}::{case.name}')
See example/xunit_report.py in the repository for a complete, runnable
walk-through.