From c1a56edcfe4fe78d7fe0788ea03462f8f2a9c5b9 Mon Sep 17 00:00:00 2001 From: Michael Marcus Date: Sat, 15 Nov 2025 11:55:41 -0500 Subject: [PATCH] Enhance pytest support Add robust test schema for pytest report Update README with sample pytest command --- README.md | 8 +- __tests__/__outputs__/python-xunit-pytest.md | 26 +++++ ...thon-xunit.md => python-xunit-unittest.md} | 0 .../__snapshots__/python-xunit.test.ts.snap | 105 ++++++++++++++++++ __tests__/fixtures/python-xunit-pytest.xml | 42 +++++++ __tests__/python-xunit.test.ts | 25 ++++- 6 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 __tests__/__outputs__/python-xunit-pytest.md rename __tests__/__outputs__/{python-xunit.md => python-xunit-unittest.md} (100%) create mode 100644 __tests__/fixtures/python-xunit-pytest.xml diff --git a/README.md b/README.md index 0563641..a7c7153 100644 --- a/README.md +++ b/README.md @@ -357,9 +357,13 @@ Please update Mocha to version [v9.1.0](https://github.com/mochajs/mocha/release Support for Python test results in xUnit format is experimental - should work but it was not extensively tested. -For pytest support, configure [JUnit XML output](https://docs.pytest.org/en/stable/how-to/output.html#creating-junitxml-format-files) and run with the `--junit-xml` option, which also lets you specify the output path for test results. +For **pytest** support, configure [JUnit XML output](https://docs.pytest.org/en/stable/how-to/output.html#creating-junitxml-format-files) and run with the `--junit-xml` option, which also lets you specify the output path for test results. -For unittest support, use a test runner that outputs the JUnit report format, such as [unittest-xml-reporting](https://pypi.org/project/unittest-xml-reporting/). +```shell +pytest --junit-xml=test-report.xml +``` + +For **unittest** support, use a test runner that outputs the JUnit report format, such as [unittest-xml-reporting](https://pypi.org/project/unittest-xml-reporting/).
diff --git a/__tests__/__outputs__/python-xunit-pytest.md b/__tests__/__outputs__/python-xunit-pytest.md new file mode 100644 index 0000000..7b13e28 --- /dev/null +++ b/__tests__/__outputs__/python-xunit-pytest.md @@ -0,0 +1,26 @@ +![Tests failed](https://img.shields.io/badge/tests-6%20passed%2C%202%20failed%2C%202%20skipped-critical) +|Report|Passed|Failed|Skipped|Time| +|:---|---:|---:|---:|---:| +|[fixtures/python-xunit-pytest.xml](#user-content-r0)|6 ✅|2 ❌|2 ⚪|19ms| +## ❌ fixtures/python-xunit-pytest.xml +**10** tests were completed in **19ms** with **6** passed, **2** failed and **2** skipped. +|Test suite|Passed|Failed|Skipped|Time| +|:---|---:|---:|---:|---:| +|[pytest](#user-content-r0s0)|6 ✅|2 ❌|2 ⚪|19ms| +### ❌ pytest +``` +tests.test_lib + ✅ test_always_pass + ✅ test_with_subtests + ✅ test_parameterized[param1] + ✅ test_parameterized[param2] + ⚪ test_always_skip + ❌ test_always_fail + assert False + ⚪ test_expected_failure + ❌ test_error + Exception: error + ✅ test_with_record_property +custom_classname + ✅ test_with_record_xml_attribute +``` \ No newline at end of file diff --git a/__tests__/__outputs__/python-xunit.md b/__tests__/__outputs__/python-xunit-unittest.md similarity index 100% rename from __tests__/__outputs__/python-xunit.md rename to __tests__/__outputs__/python-xunit-unittest.md diff --git a/__tests__/__snapshots__/python-xunit.test.ts.snap b/__tests__/__snapshots__/python-xunit.test.ts.snap index 555d41f..f325c84 100644 --- a/__tests__/__snapshots__/python-xunit.test.ts.snap +++ b/__tests__/__snapshots__/python-xunit.test.ts.snap @@ -1,5 +1,110 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +exports[`python-xunit pytest report report from python test results matches snapshot 1`] = ` +TestRunResult { + "path": "fixtures/python-xunit-pytest.xml", + "suites": [ + TestSuiteResult { + "groups": [ + TestGroupResult { + "name": "tests.test_lib", + "tests": [ + TestCaseResult { + "error": undefined, + "name": "test_always_pass", + "result": "success", + "time": 2, + }, + TestCaseResult { + "error": undefined, + "name": "test_with_subtests", + "result": "success", + "time": 5, + }, + TestCaseResult { + "error": undefined, + "name": "test_parameterized[param1]", + "result": "success", + "time": 0, + }, + TestCaseResult { + "error": undefined, + "name": "test_parameterized[param2]", + "result": "success", + "time": 0, + }, + TestCaseResult { + "error": undefined, + "name": "test_always_skip", + "result": "skipped", + "time": 0, + }, + TestCaseResult { + "error": { + "details": "def test_always_fail(): + > assert False + E assert False + + tests/test_lib.py:25: AssertionError + ", + "line": undefined, + "message": "assert False", + "path": undefined, + }, + "name": "test_always_fail", + "result": "failed", + "time": 0, + }, + TestCaseResult { + "error": undefined, + "name": "test_expected_failure", + "result": "skipped", + "time": 0, + }, + TestCaseResult { + "error": { + "details": "def test_error(): + > raise Exception("error") + E Exception: error + + tests/test_lib.py:32: Exception + ", + "line": undefined, + "message": "Exception: error", + "path": undefined, + }, + "name": "test_error", + "result": "failed", + "time": 0, + }, + TestCaseResult { + "error": undefined, + "name": "test_with_record_property", + "result": "success", + "time": 0, + }, + ], + }, + TestGroupResult { + "name": "custom_classname", + "tests": [ + TestCaseResult { + "error": undefined, + "name": "test_with_record_xml_attribute", + "result": "success", + "time": 0, + }, + ], + }, + ], + "name": "pytest", + "totalTime": 19, + }, + ], + "totalTime": undefined, +} +`; + exports[`python-xunit unittest report report from python test results matches snapshot 1`] = ` TestRunResult { "path": "fixtures/python-xunit-unittest.xml", diff --git a/__tests__/fixtures/python-xunit-pytest.xml b/__tests__/fixtures/python-xunit-pytest.xml new file mode 100644 index 0000000..fcb044a --- /dev/null +++ b/__tests__/fixtures/python-xunit-pytest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + /Users/mike/Projects/python-test/tests/test_lib.py:20: skipped + + + + def test_always_fail(): + > assert False + E assert False + + tests/test_lib.py:25: AssertionError + + + + + + + def test_error(): + > raise Exception("error") + E Exception: error + + tests/test_lib.py:32: Exception + + + + + + + + + + diff --git a/__tests__/python-xunit.test.ts b/__tests__/python-xunit.test.ts index 9959ce0..c1550a4 100644 --- a/__tests__/python-xunit.test.ts +++ b/__tests__/python-xunit.test.ts @@ -15,9 +15,9 @@ describe('python-xunit unittest report', () => { const fixturePath = path.join(__dirname, 'fixtures', 'python-xunit-unittest.xml') const filePath = normalizeFilePath(path.relative(__dirname, fixturePath)) const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'}) + const outputPath = path.join(__dirname, '__outputs__', 'python-xunit-unittest.md') it('report from python test results matches snapshot', async () => { - const outputPath = path.join(__dirname, '__outputs__', 'python-xunit.md') const trackedFiles = ['tests/test_lib.py'] const opts: ParseOptions = { ...defaultOpts, @@ -68,3 +68,26 @@ describe('python-xunit unittest report', () => { expect(report).toMatch(/^# My Custom Title\n/) }) }) + +describe('python-xunit pytest report', () => { + const fixturePath = path.join(__dirname, 'fixtures', 'python-xunit-pytest.xml') + const filePath = normalizeFilePath(path.relative(__dirname, fixturePath)) + const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'}) + const outputPath = path.join(__dirname, '__outputs__', 'python-xunit-pytest.md') + + it('report from python test results matches snapshot', async () => { + const trackedFiles = ['tests/test_lib.py'] + const opts: ParseOptions = { + ...defaultOpts, + trackedFiles + } + + const parser = new PythonXunitParser(opts) + const result = await parser.parse(filePath, fileContent) + expect(result).toMatchSnapshot() + + const report = getReport([result]) + fs.mkdirSync(path.dirname(outputPath), {recursive: true}) + fs.writeFileSync(outputPath, report) + }) +})