测试结果数据结构:JSON/XML/YAML 格式解析与标准化
搞回归测试的人,每天都要跟各种测试结果打交道。你想想看,不同框架吐出来的结果,格式五花八门——有的给 JSON,有的塞 XML,还有的用 YAML。如果不把它们统一成一套结构,后续的分析、告警、历史对比根本没法自动跑起来。
这一节,我就从实战角度聊聊这三种主流格式怎么解析,字段含义到底是什么,以及最重要的——怎么标准化。
1. 三种格式,各有各的脾气
JSON——现在的测试框架大多默认吐 JSON,比如 Jest、Mocha、Pytest 加上 --json 参数。轻量、直接,解析起来不费劲。但我记得有一次,某个服务返回的 JSON 里嵌套了 8 层对象,每次读取 results[0].tests[2].failures[0].message 写到手抽筋。
XML——Java 圈子特别喜欢用 XML,比如 JUnit 报告就是标准的 XML 格式。优点是 schema 约束强,字段定义很死,不容易出现歧义。缺点嘛……你写过 XPath 就知道了,反人类。而且文件体积大,跑完一万条 case,XML 报告能顶好几 MB。
YAML——Ruby 社区和部分 CI 工具(比如 GitHub Actions)喜欢用。可读性最好,能写注释,但它对缩进非常敏感。我在项目中遇到过因为某个字段前多了一个空格,整个解析崩掉的情况,排查了半天才发现。
核心结论:格式本身不重要,重要的是字段语义必须清晰。
2. 字段含义——你得知道每段数据在说什么
不管哪种格式,最终我们关心的数据模型大同小异。下面这张表是我自己整理的「最小必备字段集」,建议直接拿来用。
| 字段名 | 类型 | 含义 | 标准化建议 |
|---|---|---|---|
| suite_name | string | 测试套件名称 | 优先从 CI 环境变量注入,避免硬编码 |
| test_name | string | 单个用例名称 | 支持通配符匹配 |
| status | enum | 执行结果:passed/failed/skipped/error | **统一枚举值,不要出现 SUCCESS/FAILURE/ERROR 混用** |
| duration_ms | integer | 执行耗时(毫秒) | 强制使用毫秒,不接收秒或微秒 |
| failure_message | string | 失败原因摘要 | 允许为空,但必须有该字段 |
| failure_detail | string | 详细堆栈或日志 | 过长时截断前 2000 字符 |
| timestamp | string(ISO8601) | 执行完成时间 | **统一用 UTC + 毫秒,例如 2025-04-08T14:30:00.123Z** |
| tags | array[string] | 自定义标签 | 用于分类和过滤 |
⚠️ 我曾经踩过的坑:某个 CI 脚本里用了 status: "PASS",另一个地方写 status: "passed",第三个地方直接丢 status: true。标准化之前,每次汇总都得写好几个正则去匹配。后来我直接在前置步骤里做了一次 枚举归一化,所有非标准值都转成小写 enum,世界清净了。
3. 如何标准化?三步走,不废话
说白了,标准化不是重写测试框架,而是在消费端做一个 格式适配层。我自己在团队里推了下面这个流程,效果还不错。
- 识别来源格式——根据文件后缀或 MIME type 判断是 JSON 还是 XML 还是 YAML。
- 解析原始结构——用对应语言的解析器读出所有原始字段。
- 字段映射——把原始字段映射到前面那张表的标准字段名。这一步可以用一个配置文件做映射规则。
举个例子,假设 JUnit XML 里这样的片段:
<testcase name="testLogin" classname="com.example.LoginTest" time="0.023">
<failure message="Expected 200, got 401" type="AssertionError">
java.lang.AssertionError: Expected 200, got 401
at com.example.LoginTest.testLogin(LoginTest.java:42)
</failure>
</testcase>
映射之后变成:
{
"suite_name": "com.example.LoginTest",
"test_name": "testLogin",
"status": "failed",
"duration_ms": 23,
"failure_message": "Expected 200, got 401",
"failure_detail": "java.lang.AssertionError: Expected 200, got 401\n ...",
"timestamp": "2025-04-08T14:30:00.123Z",
"tags": []
}
你看,字段名统一了,时间单位统一了,状态枚举统一了。后面做趋势分析、失败率统计,完全不用关心原始格式。
小技巧:在 CI 流程里加一个「结果验证」步骤,读取标准化后的 JSON 做 schema 校验。如果某个字段缺失或类型不对,直接让流程失败,避免脏数据流到下游。
4. 格式解析的常见问题与应对
- JSON 里的空数组 vs null——有些框架失败时返回
"failures": [],有些直接"failures": null。标准化时统一转成空数组。 - XML 的 CDATA 和特殊字符——解析时务必保留原始内容,不要丢失 escape。我见过有人用 regex 解析 XML 导致 stack trace 被截断,非常坑。
- YAML 的多行字符串——用
|或>表示折叠,解析后要注意换行符的处理,否则后续拼接到告警消息里排版会乱。
嗯,这里再强调一点:不管用什么格式,千万别在结果里写二进制或 base64 大段数据。标准化之前先把日志里的截图文件路径剥离出来,单独存到对象存储,结果里只留链接。
5. 我个人的标准化实践
我在维护一个大型 CI 平台的时候,每天要处理来自 20 多个团队的回归结果。最初大家各写各的,有的用 Python 的 unittest JSON 输出,有的用 JUnit XML,还有的直接在 Jenkins 里写 console output 正则提取。
后来我写了一个轻量的 结果归一化服务,流程很简单:
- 监听指定目录或 S3 bucket 的新文件
- 根据文件后缀选择解析器
- 用 JSON Schema 校验并映射
- 写入统一的 Elasticsearch 索引
这么一搞,后续的看板、失败分析、Flaky Test 检测都只需要查询一个索引,维护成本直线下降。
一句话总结:测试结果标准化不是技术难题,而是管理规范问题。你只要把字段字典定义清楚,把解析和映射写成一个函数,剩下的就是监督大家照做。
最后提醒一句:标准化之后,记得保留一份原始原始格式的备份。万一映射逻辑有 bug,还能回退重算。