如何及时发现 API 模式漂移,避免测试漏检?
只检查状态码和几个字段的测试,在 API 新增字段、删除未断言的字段,或将数字改为字符串时,仍会通过。Nexus Studio 会存储每个 API 响应的结构,仅记录字段名称和类型,绝不记录值,并与上一次运行进行比较,结构变化时报告 API_DRIFTED。启用 NEXUS_API_DRIFT_STRICT=true 后,同样的变化会导致测试套件失败。
由 Verdict System LLC 的 Nexus Studio 团队撰写。2026年10月27日。
什么是 API 模式漂移?
模式漂移是指响应结构在两次运行之间发生变化:新增字段、删除字段,或字段类型改变。API 可以演进。问题在于,测试套件没有提示任何变化,使用该 API 的应用直到生产环境中才发现。
为什么通过的契约测试会漏掉这种变化?
因为它只会对编写者想到的内容进行断言。例如,validate(res, { status: 200, hasFields: ['id'] }) 这条规则在 price 从数字变为字符串时仍会通过,因为规则中没有 price。真正出问题的是对价格求和的客户端。
能用一个可运行的示例演示吗?
将以下内容保存为 server.mjs。它提供一个端点,由 SHAPE 变量决定端点返回什么:数字形式的价格,或字符串形式的价格并附带一个新增的 currency 字段。
import http from 'node:http';
// SHAPE=v1 answers {id, price: 9.99}; SHAPE=v2 answers {id, price: "9.99", currency: "USD"}.
const v2 = process.env.SHAPE === 'v2';
http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(v2 ? { id: 7, price: '9.99', currency: 'USD' } : { id: 7, price: 9.99 }));
}).listen(4010, '127.0.0.1');
将这个 API 测试保存为工作区 api-tests/rest 文件夹中的 APIT900_drift_demo.api.js。它只断言状态码和一个字段,许多实际的测试套件也是如此。
// type: rest
import { runApiSuite } from '../../utils/base-api-fixture.js';
await runApiSuite([{
id: 'APIT900-1',
name: 'GET /product/7 returns 200 with an id',
fn: async ({ request, validate }) => {
const res = await request({ method: 'GET', url: '/product/7' });
validate(res, { status: 200, hasFields: ['id'] });
},
}]);
在一个 PowerShell 窗口中启动服务器,在另一个窗口中运行测试。首次运行时没有可供比较的记录,因此只会记录结构并通过测试。
$env:SHAPE = "v1"; node server.mjs
$env:API_BASE_URL = "http://127.0.0.1:4010"; nexus api APIT900 --no-app
停止服务器,设置 $env:SHAPE = "v2" 后重新启动,再运行同一个测试。测试仍然通过,但本次运行不再是 API_SECURE。以下是输出,仅保留了关键行:
schema drift: +1 field, 1 type change
+ currency
~ price: number → string
API_DRIFTED 7ms
1/1 passed
1 endpoint changed shape since the last run, set NEXUS_API_DRIFT_STRICT=true to fail the suite on it
漂移能导致构建失败吗?
可以,按需启用即可。默认情况下,API_DRIFTED 视为通过,退出码为 0,因为 API 发生变化是一条信息,而非缺陷。它会单独输出一行,并与差异一起记录在本次运行的证据中,因此不会被当作完全无异常的通过。设置 NEXUS_API_DRIFT_STRICT=true 后,同样的变化会使退出码变为 1。要查看效果,请设置该变量,然后分别针对 v1 和 v2 各运行一次测试。
基线始终是上一次运行。发生漂移后,新结构就成为基线,因此下次针对相同响应运行时,不会报告漂移。
有哪些局限?
- 测试首次运行时只记录结构。此时还没有可用于判断漂移的基准。
- 结构包含名称和类型,而非含义。如果
price仍是数字,但单位从美元变成美分,结构不会发生变化。 - 比较对象是上一次运行,而非事先约定的契约。它告诉你发生了变化,而不会判断这种变化是否被允许。
- 每个测试用例保留一个结构,取自其最后一次请求的正文。列表以第一个元素描述,对象最多记录 200 个键,嵌套深度最多为六层。
在哪里可以看到?
判定结果指南介绍了测试判定结果,证据页面列出了每次运行会在磁盘上留下的文件。30 天试用版可在你自己的 API 测试上运行同一引擎。