pytest 结果报告丢失速查:输出目录被清、工作目录漂移与 junitxml 路径配置
更新时间:2026-10-24。FAQ 速查页,面向测试跑完但 CI 页面上「No tests found」、junitxml 文件凭空消失、或 HTML 报告打开一片空白的同学。pytest 报告丢失的本质是「报告确实生成了,但不在你以为的地方,或者生成后又被清掉了」:写报告时的工作目录和收集器找报告时的基准目录不是同一个、流水线的 cleanup 步骤把输出目录一起带走、多个并行任务往同一个文件写互相覆盖、以及 junitxml 配的是相对路径而工作目录悄悄漂了。病因收敛在六根轴上:输出目录被清理步骤误删、CI 各阶段工作目录不一致、相对路径基准漂移、并发写覆盖、目录无写权限导致静默失败、以及上传/收集器的路径模式没匹配上。顺一遍基本必中。如果是测试本身没跑起来而不是报告丢了,转 Python 退出码非零速查。
本文要回答的问题
- pytest 明明跑了,为什么 CI 显示 No tests found?
--junitxml写的是相对路径,文件到底落在哪?- 本地有报告、流水线里没有,差在哪个环节?
- 并行跑测试时报告为什么只剩最后一个分片的?
- HTML 报告生成了但打开空白,是丢了吗?
一、报错原文与典型现象
报告丢失很少直接报错,更多表现为「找不到」(路径一律以 /proj/demo 占位):
# CI 收集器找不到产物
No test result files matching /proj/demo/reports/junit-*.xml were found
# 或
No files were found with the provided path: reports/# pytest 写报告失败(权限或目录不存在)
ERROR: file or directory not found: /proj/demo/reports/junit.xml
internalerror: OSError: [Errno 13] Permission denied: '/proj/demo/reports/junit.xml'读现象先分两类:收集器说「No files found」是产物路径对不上——报告可能在但不在收集器看的地方;pytest 自己抛 OSError/Permission denied 是写盘环节失败——报告压根没生成。前者查路径一致性,后者查写权限与目录存在性。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| 收集器 No files found | 报告路径与收集器基准目录不一致 | 在 CI 里加一步 find 全仓搜 xml | 报告与收集配置统一用绝对基准 |
| 本地有、流水线没有 | cleanup 或缓存步骤把输出目录清了 | 看各步骤日志里有没有 rm 痕迹 | 报告目录加入保留清单,上传提前 |
| 并行跑只剩一个分片结果 | 多个 worker 写同一 junitxml 文件互相覆盖 | 文件名是否所有分片共用 | 文件名带分片编号,汇总用合并工具 |
| OSError Permission denied | 输出目录属主与运行用户不符 | ls -ld 看目录属主 | 修正属主或换可写目录 |
| internalerror 目录不存在 | 相对路径基准漂移,目标目录没建 | pwd 对比预期工作目录 | 用绝对路径并先 mkdir -p |
| HTML 报告打开空白 | 资源被 CSP 拦或自包含开关没开 | 浏览器控制台看报错 | pytest-html 加 --self-contained-html |
三、排查决策图
下面这张图是标准排查路线,颜色按角色区分:红是报错入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓分水岭:pytest 日志里有 OSError 走左支查写盘三要素(目录、权限、并发);pytest 正常结束但收集器说找不到走右支查路径一致性——八成是报告落点和收集器基准目录对不上。节点共 7 个,主干是「定环节 → 分流 → 统一路径基准 → 重跑验证」。
四、逐条排查展开
第一问永远是:报告到底生成了没有。 在流水线里 pytest 步骤之后紧跟一条 find . -name "junit*.xml" -o -name "*.html" 把产物落点打出来,与收集器配置的路径逐字符对比——这一步能区分 80% 的「写盘失败」与「路径对不上」。报告在而收集器说没有,就是路径问题;报告不在,往写盘方向查。
相对路径是漂移重灾区。 --junitxml=reports/junit.xml 里的 reports/ 相对的是 pytest 启动时的工作目录,而 CI 各步骤的工作目录未必相同:测试在容器 A 的 /proj/demo 跑,收集步骤却在宿主机的另一个挂载点找。修法:所有报告路径用绝对路径或显式基于仓库根的变量拼接,pytest 启动前先 mkdir -p 目标目录(pytest 不会替你建目录,目录不存在直接 internalerror)。
清理步骤是报告的头号杀手。 很多流水线模板带「构建后清理」步骤,rm -rf 的范围写得粗,把 reports 目录一起带走;还有的把整个工作区缓存策略配成「只保留源码」。排查时把 cleanup、cache、artifact 三个关键词在流水线配置里各搜一遍。修法:报告目录显式列入保留/上传清单,且上传步骤排在任何清理之前。
并行分片必须各写各的文件。 pytest-xdist 或手动分片跑测试时,如果所有分片共用同一个 --junitxml=reports/junit.xml,后完成的分片直接覆盖先完成的,最终只剩最后一片的结果。标准做法:文件名带分片标识(junit-${CI_NODE_INDEX}.xml),收集器用通配符 junit-*.xml 全收,或者事后用 junitparser 之类工具合并成一份。
权限问题爱静默。 容器里以 root 跑测试、宿主机收集步骤以普通用户读,或者输出目录挂在只读卷上,都会让报告「写了个寂寞」。pytest 对写盘失败多数会抛 internalerror 让任务红掉,但某些插件组合下只是警告。流水线上跑 id 和 ls -ld reports/ 各一次,属主与写权限一眼可见。
HTML 空白不是丢,是资源没带上。 pytest-html 默认把 CSS/JS 外链,CI 产物页或邮件里打开时常被安全策略拦掉,表现就是空白页。加 --self-contained-html 让资源内联进单个 HTML 文件,空白即消。
五、规范做法与预防
- 报告路径绝对化:流水线里统一定义 REPORTS_DIR 变量,pytest 与收集器引用同一处。
- 先建目录再跑测试:pytest 前
mkdir -p输出目录,写进模板。 - 上传先于清理:artifact 上传步骤永远排在 cleanup 之前,报告目录进保留清单。
- 分片独立命名:并行场景文件名带分片号,收集用通配,禁止共用单文件。
- 产物自检步骤:测试步骤后 find 打印产物清单,缺了立刻 fail,不等到收集环节才暴露。
- HTML 报告自包含:固定带
--self-contained-html,交付物单文件化。
这一篇在知识体系里的位置
本篇是 Python 排错速查簇的测试产物入口篇:测试进程本身异常退出看 Python 退出码非零速查;输出目录属主不对看 Python 权限不足速查;报告写到一半被截断看 Python 文件截断速查;测试时好时坏看 Python flaky 不确定性速查。「先 find 确认报告生成与否 → 生成查路径一致性、没生成查写盘三要素」这条主线对一切报告丢失类报错通用。
一句话总结
pytest 报告丢失先在流水线里 find 一把确认报告到底生成没:生成了而收集器说 No files found,就是路径基准不一致——junitxml 的相对路径跟着工作目录漂,统一改用绝对路径变量并先 mkdir -p;没生成则查写盘三要素:目录存在性、写权限、并行分片是否共用单文件互相覆盖。流水线 cleanup 步骤误清输出目录是本地有而线上没有的固定剧本,上传必须排在清理之前。路径模板化、产物自检步骤兜底、HTML 报告加自包含开关,这类报错基本绝迹。