一句话:文档写得再漂亮,只要测试没闭环,就是定时炸弹。本文给出一套"文档即测试"的落地流程,让研发、测试、产品都能用同一套"可执行文档"对话,把缺陷拦截在发版前。
一、为什么要对"文档"做测试?
-
文档是"最早的接口实现"------比代码还早
-
文档错误 = 联调返工:前端按错字段渲染,App 发版即事故
-
传统"写完代码再补文档"导致"文档永远落后代码"
-
把文档变成可执行测试用例,才能持续保鲜(Living Documentation)
二、测试目标全景图
| 维度 | 通过标准 | 失败后果 |
|---|---|---|
| 结构 | 100 % 符合 OpenAPI 语法 | Mock 服务起不来 |
| 语义 | 字段名、类型、取值与线上一致 | 前端白屏/崩溃 |
| 业务 | 所有状态码、异常分支可列举 | 漏测导致资损 |
| 性能 | 文档里的"SLA"可被压测脚本引用 | 大促超时雪崩 |
| 安全 | 敏感字段脱敏、鉴权方式可验证 | 数据泄露 |
| 变更 | 每次 MR 自动 diff,向下兼容 | 误删字段引发事故 |
三、四层测试模型
API 文档测试
├─ 静态层:语法 & 规范
├─ 契约层:请求 / 响应 & 实例
├─ 性能层:SLA & 压力
└─ 安全层:鉴权 & 脱敏
四、工具选型(全部开源可商用)
| 层级 | 工具 | 作用 |
|---|---|---|
| 静态 | Spectral / vacuum | OpenAPI 语法 + 自定义规范 |
| 契约 | Dredd / schemathesis / Postman CLI | 文档 vs 真实服务交叉验证 |
| Mock | Prism / MockServer | 用文档零代码起 Mock |
| 性能 | K6 / Gatling | 直接引用文档里的 x-sla 字段 |
| 安全 | 42Crunch / Swagger-Scan | 静态扫描 + 动态渗透 |
| CI | GitHub Actions / GitLab CI | MR 阶段自动跑全套 |
五、实战:一条流水线跑通"文档即测试"
5.1 目录约定
project/
├─ api/
│ ├─ openapi.yaml
│ └─ rulesets/.spectral.yml # 自定义规范
├─ tests/
│ ├─ contract/ # 契约测试脚本
│ ├─ perf/ # K6 性能脚本
│ └─ security/ # 42Crunch 扫描报告
├─ .github/workflows/api-test.yml
└─ Makefile
5.2 静态测试:让"错别字"在提交阶段就失败
# .spectral.yml
extends: "spectral:oas"
rules:
operation-operationId: error
path-param-required: error
api-must-have-examples: error # 每个 schema 必须带 example
GitHub Actions 片段:
- name: Lint API
run: |
npx spectral lint api/openapi.yaml --ruleset api/rulesets/.spectral.yml
实测:300+ 接口的项目,平均每周拦截 12 处"漏写 example""大小写不一致"等低级错误。
5.3 契约测试:文档与代码"互相印证"
-
用 Postman 生成 Collection(自动带 example)
-
一条命令跑交叉验证:
newman run api-collection.json
--environment test.postman_environment.json
--reporters cli,json
--reporter-json-export newman-report.json -
把报告上传到 CI,失败即阻塞 MR
技巧:在 OpenAPI 里加
x-test-hooks,声明"登录 → 创建订单 → 取消订单"场景,newman 自动按序执行,实现"场景级"契约测试。
5.4 性能测试:把文档里的 SLA 变成可执行脚本
在 openapi.yaml 里加自定义扩展:
paths:
/order:
post:
x-sla:
p95: 300ms
target-rps: 1000
K6 脚本自动生成:
javascript
import http from 'k6/http';
import { check } from 'k6';
import openapi from './openapi.json';
const sla = openapi.paths['/order'].post['x-sla'];
export let options = {
stages: [
{ duration: '1m', target: sla['target-rps'] },
{ duration: '3m', target: sla['target-rps'] },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: [`p(95)<${sla['p95']}`],
},
};
export default function () {
let res = http.post(__ENV.BASE_URL + '/order', JSON.stringify({
skuId: __ENV.SKU_ID,
qty: 1,
}), { headers: { 'Content-Type': 'application/json' } });
check(res, {
'status is 201': (r) => r.status === 201,
});
}
CI 里跑:
- name: Performance test
run: |
docker run --rm -i -v $PWD:/src grafana/k6 run /src/tests/perf/order.js \
-e BASE_URL=https://staging.example.com -e SKU_ID=12345
5.5 安全测试:把"脱敏"写进文档,再自动扫描
-
在 response schema 里加
x-sensitive: trueUser:
type: object
properties:
id:
type: integer
mobile:
type: string
x-sensitive: true
description: "需脱敏返回,如 138****8888" -
用 42Crunch 扫描:
docker run --rm -v PWD/api:/data 42crunch/security-audit \ -f "/data/openapi.yaml" -t {42C_API_TOKEN}
-
报告直接回写 MR 评论,并阻断合并
六、变更回归:如何"一眼"看出字段被删?
利用 oasdiff:
oasdiff -base api/openapi-base.yaml -revision api/openapi.yaml \
-format text -breaking-only
在 CI 里:
- name: Breaking change check
run: |
oasdiff -base api/openapi-base.yaml -revision api/openapi.yaml \
-breaking-only > diff.txt
if [ -s diff.txt ]; then
echo "❌ 发现破坏性变更"; cat diff.txt; exit 1
fi
效果:
-
字段删除、类型收窄、必填新增 → 阻断
-
仅增加可选字段 → 通过
七、ROI 复盘:数字说话
| 指标 | 落地前 | 落地 3 个月 |
|---|---|---|
| 联调返工工时 / 人/月 | 38 h | 9 h ↓76 % |
| 线上事故(API 相关) | 5 起 | 0 起 |
| 文档"最新"率 | 62 % | 98 % |
| 新人文档上手时间 | 3 d | 0.5 d |
八、小结:把"文档测试"刻进流水线
-
静态语法 → 提交阶段拦截 typo
-
契约验证 → MR 阶段拦截语义漂移
-
性能/安全 → 合并前验证 SLA & 合规
-
变更 diff → 破坏性改动自动亮红灯
当文档成为"可执行的单测",它就再也不会腐烂;而 API 的稳定性,也就从"事后救火"变成了"事前拦截"。