pytest-testcase-collector
一个 pytest 插件,可自动收集测试用例报告数据(名称、级别、类型、所属类/模块、代码仓等),并生成指定格式的 XML 文件。
核心功能
- 元数据采集:提取用例名称、级别(
@pytest.mark.case_info(level))、类型(@pytest.mark.case_info(type))、所属类/模块信息,支持参数化用例 - Git 信息集成:自动识别 Git 仓库,获取仓库 URL、分支信息(非 Git 仓库自动兼容)
- XML 生成:按固定结构生成包含流水线/任务标识的 XML 元数据文件
- Flaky 重试支持:支持标记不稳定测试用例进行自动重试,满足最小通过次数要求
- 环境变量控制:支持通过环境变量控制 JUnit XML 输出和元数据收集行为
- 灵活配置:支持命令行指定输出路径、流水线 ID 等参数,提供完善的错误处理
快速开始
1. 安装插件
方式 1:从 whl 包安装(推荐)
# 先构建 whl 包(需提前安装 build 模块)
pip install build
python -m build --wheel
# 安装生成的whl包
pip install dist/pytest_report_uploader-1.0.0-py3-none-any.whl
方式 2:源码开发模式安装
pip install -e .
2. 使用示例
仅收集用例 + 生成元数据(推荐)
pytest test_first.py --pipeline-id 123456 --pipeline-run-id 789012 --job-id 345678 --collect-only
运行测试 + 生成元数据
pytest test_first.py --pipeline-id 123456 --pipeline-run-id 789012 --job-id 345678
使用环境变量控制行为
# 启用自动 JUnit XML 输出
export ENABLE_JUNITXML_OUTPUT=1
export RESULTS_XML_DIR=./test-results
pytest test_first.py
# 禁用元数据收集
export DISABLE_METADATA_COLLECTION=1
pytest test_first.py
3. 命令行参数说明
所有参数均为可选,未指定则对应字段留空
| 参数 | 说明 | 默认值 |
|---|---|---|
--pipeline-id |
流水线唯一标识 | 无 |
--pipeline-run-id |
流水线运行记录 ID | 无 |
--job-id |
任务/作业唯一标识 | 无 |
--metadata-output |
元数据 XML 输出文件路径 | metadata.xml |
4. 环境变量说明
| 环境变量 | 说明 | 默认值 |
|---|---|---|
ENABLE_JUNITXML_OUTPUT |
设置为 1/true/yes 时自动添加 --junitxml 参数 |
不启用 |
RESULTS_XML_DIR |
JUnit XML 输出目录 | results_xml_dir |
DISABLE_METADATA_COLLECTION |
设置为 1/true/yes 时禁用元数据收集和 XML 生成 |
启用 |
Flaky 测试重试
使用方式
import pytest
@pytest.mark.flaky(reruns=3, reruns_delay=2, min_pass=2)
def test_flaky_test():
"""不稳定的测试用例,最多重试3次,每次间隔2秒,至少通过2次"""
import random
assert random.choice([True, False, True])
参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
reruns |
重试次数 | 1 |
reruns_delay |
重试间隔(秒) | 0 |
min_pass |
最小通过次数(需同时设置 reruns > 0) | 1 |
注意:只有同时设置 reruns > 0 和 min_pass > 0 时,才会启用自定义重试逻辑。
示例展示
测试用例示例
import pytest
@pytest.mark.case_info(level="L0", type="Functional")
def test_first():
assert 1 == 1
@pytest.mark.case_info(level="L1", type="Functional")
@pytest.mark.parametrize("a,b", [("3+5", 8), ("3+2", 5)])
def test_second(a, b):
assert eval(a) == b
@pytest.mark.flaky(reruns=3, min_pass=2)
def test_flaky_example():
import random
assert random.choice([True, False])
生成的 XML 示例
<?xml version='1.0' encoding='utf-8'?>
<metadata>
<pipelineId>123456</pipelineId>
<pipelineRunId>789012</pipelineRunId>
<jobId>345678</jobId>
<frameType>pytest</frameType>
<repoUrl>https://gitcode.com/openlibing/openlibing-pytest-executor.git</repoUrl>
<repoBranch>main</repoBranch>
<testCases>
<testCase>
<name>test_first</name>
<level>L0</level>
<type>Functional</type>
<className>test_first</className>
<filePath>test_first.py</filePath>
</testCase>
<testCase>
<name>test_second[3+5-8]</name>
<level>L1</level>
<type>Functional</type>
<className>test_first</className>
<filePath>test_first.py</filePath>
</testCase>
<testCase>
<name>test_second[3+2-5]</name>
<level>L1</level>
<type>Functional</type>
<className>test_first</className>
<filePath>test_first.py</filePath>
</testCase>
</testCases>
</metadata>
技术架构
整体流程
命令行参数处理 → 测试用例收集 → 元数据提取 → Git 仓库信息获取 → XML 文件生成 → 测试执行(支持 flaky 重试)→ JUnit XML 修改
项目结构
pytest-testcase-collector/
├── pytest_testcase_collector/ # 插件主包
│ ├── __init__.py # 包初始化
│ ├── conftest.py # pytest 钩子实现(核心)
│ ├── metadata_collector.py # 元数据收集核心逻辑
│ ├── flaky_rerun.py # flaky 用例重试处理
│ ├── git_info.py # Git 信息获取
│ ├── xml_generator.py # XML 生成
│ └── error_handler.py # 错误处理
├── test_first.py # 测试示例
├── readMe.md # 说明文档
├── setup.py # 安装配置
├── pyproject.toml # 依赖与元数据管理
└── pytest.ini # pytest 配置
核心钩子函数
| 函数 | 作用 | 执行时机 |
|---|---|---|
pytest_load_initial_conftests |
根据环境变量添加 --junitxml 参数 |
命令行参数解析后,插件初始化前 |
pytest_sessionstart |
初始化元数据存储 | 测试会话开始时 |
pytest_collection_modifyitems |
收集每个测试用例的元数据 | 测试用例收集完成后 |
pytest_collection_finish |
生成元数据 XML 文件 | 测试用例收集完成后 |
pytest_runtest_protocol |
处理 flaky 测试重试逻辑 | 每个测试用例执行前 |
pytest_sessionfinish |
修改 JUnit XML 添加 flaky 标识 | 测试会话结束时 |
错误处理
插件内置完善的异常处理机制:
- 元数据提取失败:输出明确的错误提示,不中断整体流程
- XML 生成失败:记录详细日志并提示可能的原因(如路径无写入权限)
- Git 信息获取失败:自动跳过并将
repoUrl/repoBranch置空,兼容非 Git 环境 - Flaky 重试失败:记录重试次数和结果,不影响其他测试执行
注意事项
- 使用
--collect-only参数时,仅收集用例元数据,不执行测试 - 元数据收集默认启用,可通过
DISABLE_METADATA_COLLECTION=1禁用 - JUnit XML 输出默认关闭,可通过
ENABLE_JUNITXML_OUTPUT=1启用 - Flaky 重试需要同时设置
reruns > 0和min_pass > 0才会生效
Release files for pytest-testcase-collector 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pytest_testcase_collector-1.0.0.tar.gz | 17.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_testcase_collector-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.7 kB
Release files / pytest_testcase_collector-1.0.0.tar.gz
| Download URL | pytest_testcase_collector-1.0.0.tar.gz |
|---|---|
| Size | 17.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0cc105e98450943510d4e2620162d9fabc2ad98d1be49b71e4eb8f585caf88d2
|
|
BLAKE2b-256 checksum How to use checksums |
65a36316eff1a6bf4f563e1839f5d5f20ffe45140386a8829593d1f5b9986058
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.12
|
Release files / pytest_testcase_collector-1.0.0-py3-none-any.whl
| Download URL | pytest_testcase_collector-1.0.0-py3-none-any.whl |
|---|---|
| Size | 16.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fc6ba4a79c61159b1883e8f8de2fb03ba523626d7e2c6462fd0f6c07a6d19ce7
|
|
BLAKE2b-256 checksum How to use checksums |
304aa06f0d4c973f99d7d570a4d8fb1106811ab189b0c09c8c98147d6b744e1e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.12
|