pytest 4.6.7 发布解读:修复 JUnit XML 报告结构与临时目录清理竞态问题
【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest
pytest 4.6.7 是 pytest 4.6 系列维护线的一个 bug-fix(缺陷修复)版本,于 2019-12-05 发布到 PyPI,属于可直接替换(drop-in replacement)的补丁版本。本次发布聚焦于两个关键修复:--junitxml生成的 XML 报告现在正确包含<testsuites>根元素(issue #5477),以及临时目录清理时对FileNotFoundError的妥善忽略(issue #6044)。阅读本文后,你将理解这两个修复的来龙去脉、如何在项目中安全升级 pytest,并能结合当前仓库源码掌握 JUnit 报告生成与临时目录管理机制的底层实现。
版本定位:一次安全的缺陷修复
pytest 4.6.7 的官方发布公告(doc/en/announce/release-4.6.7.rst)明确指出:
This is a bug-fix release, being a drop-in replacement.
所谓 drop-in replacement(可直接替换),意味着它只修复缺陷、不引入破坏性变更,在同一个主版本号段内升级不需要修改测试代码、插件配置或命令行参数。该版本的完整变更记录收录于仓库的 doc/en/changelog.rst,其中 4.6.7 一节(doc/en/changelog.rst#L5842-L5853)列出了两项 Bug Fixes:
- #5477:
--junitxml生成的 XML 文件现在正确包含<testsuites>根元素。 - #6044:在尝试删除旧的临时目录时,正确忽略
FileNotFoundError(Python 2 中为OSError.errno == NOENT),例如多个进程同时删除同一目录时(常见于使用pytest-xdist并行执行的情况)。
发布公告同时向本版本的贡献者表达了感谢,包括Bruno Oliveira与Daniel Hahler两位开发者。
升级方式
由于 4.6.7 是补丁版本且完全向后兼容,升级只需通过 pip 更新到最新版本:
pip install --upgrade pytest升级完成后,可以通过pytest --version校验安装的版本号。当前仓库中版本号的定义位于 src/_pytest/init.py,它从_version模块导入__version__与version_tuple(若安装损坏则回退为"unknown"),并由pytest --version展示给用户。
修复一:#5477——JUnit XML 报告根元素修正
问题背景
JUnit XML 是 CI/CD 系统(如 Jenkins、GitLab CI)解析测试结果的标准格式。pytest --junitxml=path命令把测试结果输出为 JUnit 风格的 XML 文件。在 4.6.7 之前,该 XML 文件的根元素结构存在缺陷,无法正确包含<testsuites>根元素,导致部分严格校验 XML 结构的工具或平台无法正确识别报告。
修复效果
4.6.7 修复后,--junitxml输出的 XML 以<testsuites>作为根元素,符合 JUnit 规范。如果对当前仓库版本的输出做校验,可以看到根元素还被赋予了name="pytest tests"属性(这一增强来自后来的 issue #12736,记录在 doc/en/changelog.rst 中,并明确其基于junit-10.xsd规范)。
底层实现
当前仓库的 JUnit 报告逻辑集中在 src/_pytest/junitxml.py:
LogXML类负责构建整个 XML 树,通过ET.Element(xml.etree.ElementTree)组装<testsuites>→<testsuite>→<testcase>的层级结构;_TestSuiteNode.to_xml()(src/_pytest/junitxml.py#L156-L162)负责把单个测试用例节点序列化为<testcase>元素,并写入耗时time属性;- 捕获输出与日志的写入由
write_captured_output()(src/_pytest/junitxml.py#L169-L190)完成,它会依据配置决定把捕获到的 stdout、stderr 与日志以<system-out>、<system-err>形式写入报告。
相关配置项
围绕 JUnit 报告,pytest 提供了若干 ini 配置(定义于 src/_pytest/junitxml.py#L404-L420),可在pytest.ini或pyproject.toml的[tool.pytest.ini_options]中设置:
| 配置项 | 默认值 | 说明 |
|---|---|---|
junit_suite_name | pytest | JUnit 报告中测试套件的名称 |
junit_logging | no | 是否把捕获的日志写入报告,可选no、log、system-out、system-err、out-err、all |
junit_log_passing_tests | True | 是否对通过的测试也记录捕获的输出(设为False可减小报告体积) |
junit_duration_report | — | 测试耗时的统计口径 |
例如在pytest.ini中:
[pytest] junit_family = xunit2 junit_suite_name = my_project junit_logging = all junit_log_passing_tests = False搭配命令行使用:
pytest --junitxml=test-results.xml修复二:#6044——临时目录清理竞态问题
问题背景
pytest 为每个测试提供tmp_path/tmpdirfixture,并在会话结束时清理这些临时目录。当测试通过pytest-xdist在多个进程中并行运行时,多个进程可能同时尝试删除同一个临时目录:一个进程删除了目录,另一个进程再去删除时就会抛出FileNotFoundError,导致整个 pytest 会话异常中断。
修复效果
4.6.7 修复后,pytest 在清理旧临时目录时会正确忽略这类"目录已被他人删除"的异常。changelog 原文特别说明这是"当多个进程尝试删除同一个目录时(常见于pytest-xdist)"的典型场景;在 Python 2 中,FileNotFoundError尚不存在,需要通过OSError.errno == NOENT来识别同一类错误。
底层实现
当前仓库中,临时目录的健壮性清理逻辑沉淀在 src/_pytest/pathlib.py:
rm_rf()封装了shutil.rmtree调用,并为 Python 3.12+ 传入onexc参数、其余版本传入onerror错误处理回调;- 错误处理回调
on_rm_rf_error()(src/_pytest/pathlib.py#L108-L120)中,对FileNotFoundError明确返回False,其源码注释直接写道:"Another process removed the file in the middle of the rm_rf (xdist for example)"——即另一进程恰好在该进程清理中途删除了文件; - 临时目录的管理入口是 src/_pytest/tmpdir.py 中的
TempPathFactory,它通过make_numbered_dir_with_cleanup、rm_rf等函数创建与回收会话级临时目录,并通过ExitStack注册会话结束时的清理动作(src/_pytest/tmpdir.py#L79-L81),即使sessionfinish钩子因故未执行,atexit兜底清理仍能生效。
对使用者的影响
对普通用户而言,此修复消除了并行测试时的偶发崩溃;对使用pytest-xdist的团队而言,它显著提升了大规模测试在 CI 上的稳定性。涉及临时目录行为的命令行选项还包括--basetemp(自定义会话级临时目录根路径),它在 src/_pytest/tmpdir.py 中被解析为TempPathFactory的given_basetemp,并参与目录的安全校验与保留策略(tmp_path_retention_policy)。
如何在当前仓库中验证这两个修复
如果你希望在当前仓库中验证这两处修复对应的实现,可以按以下路径深入阅读:
- JUnit 报告结构:src/_pytest/junitxml.py 中的
LogXML类与write_captured_output()方法,配合pytest --junitxml=out.xml运行任一测试后查看out.xml的根元素; - 临时目录清理:src/_pytest/pathlib.py 中
on_rm_rf_error()对FileNotFoundError的处理分支,以及 src/_pytest/tmpdir.py 中TempPathFactory的注册与清理流程; - 完整变更记录:仓库根目录的 doc/en/changelog.rst 收录了包括 4.6.7 在内的全部历史版本变更,可作为回溯各版本修复的权威依据;所有历史版本公告可参见 doc/en/announce/index.rst。
小结
pytest 4.6.7 虽然只是 4.6 系列的一个小补丁,但它的两个修复分别触及了测试框架的两条关键链路:对外输出(JUnit XML 报告的规范性与平台兼容性)和对内管理(临时目录的生命周期与并发安全)。理解这两个修复,既能帮助你在升级 pytest 时从容验证,也能让你在阅读当前仓库源码时快速定位 JUnit 报告与临时目录清理这两大基础设施的核心实现。
【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考