#React#TypeScript#DevOps

API Mock 与 Fixture 漂移门禁:三方契约、录制血缘与双向验证

Mock 测试全绿,真实 API 仍可能已经改了枚举、默认值或错误语义。本文把规范、生产者实现和测试替身纳入同一版本清单,用双向一致性与反例阻断“自洽但错误”的前端测试。

本文要点

  • 主题聚焦:React、TypeScript、DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年6月5日·更新于 2026年7月20日

前端仓库把 /orders 的成功响应固定成一份 JSON,半年后 API 将 status: pending 细分为 awaiting_payment,Mock 仍返回旧值,组件测试继续全绿。另一种漂移更隐蔽:OpenAPI 标明字段可空,真实服务却总是省略;团队只校验 schema,仍无法发现默认值、排序和错误码语义已分叉。REST API 版本控制讨论接口如何演进,本篇治理测试替身是否仍代表某个明确版本。

OpenAPI 3.1 的 Schema Object 基于 JSON Schema 2020-12 方言,但一份有效 schema 不会自动证明示例覆盖了所有分支,也不能证明实现遵守业务不变量。Mock、规范和生产者必须分别取证;不能让 Mock 由规范生成后,再用同一生成器验证自己并声称契约成立。API 设计对比帮助选择协议,落地后仍需要真实消费者场景与生产者组合验证。

固定 contractSet.v1 的四个摘要

每次发布测试替身生成 web.apiMockContractSet.v1。manifest 固定 apiId、OpenAPI 与 dialect、生产者和 Mock 制品、fixture bundle,还必须固定 parser、独立 verifier、normalizer、consumer artifacts 及业务 oracle bundle 的版本与摘要。每个场景有稳定 scenarioId,列出请求前置、响应分支、延迟行为、敏感字段策略及 oracle。

yaml
schema: web.apiMockContractSet.v1
apiId: orders.v3
specDigest: sha256:31d...
producerArtifact: sha256:901...
mockEngine: [email protected]
fixtureBundle: sha256:8ca...
toolchain: { parser: sha256:12a..., verifier: sha256:44b... }
normalizer: sha256:77c...
consumers: [web@sha256:51d...]
oracleBundle: sha256:93e...
scenarios: [order.paid, order.missing, order.conflict]

规范文字或语义变化必须产生新 specDigest;只重新排序键不能制造版本。fixture 是不可变内容,修正旧样例要发布新 bundle 并记录 supersedes,禁止覆盖文件后保留原版本号。reader 不支持 dialect、引用无法解析或摘要不符时返回 UNRESOLVED_CONTRACT,不能跳过验证继续启动 Mock。

三条独立验证边

第一条边由与 Mock 生成器不同实现、独立发布的 verifier 对真实生产者执行规范 conformance。第二条把同一请求送入生产者与 Mock,比较固定 normalizer 后的结果;时间、追踪 ID 可按声明忽略,排序、金额单位和缺失/空值不能抹平。第三条让 manifest 中的真实 consumer artifact 分别对两者运行。结构验证之外,独立业务 oracle 还检查金额守恒、状态转换、游标不重复和错误不产生副作用;不能只让 parser 与生成器彼此自洽。

兼容矩阵至少覆盖 previous consumer/current consumer 与 previous producer/current producer,破坏结果进入人工迁移,不自动刷新 snapshot。Agent API 契约兼容门适用于 Agent 改 schema,这里还要求 producer 制品在每个场景独占数据库、队列和邮件/支付 sink;网络默认关闭,外部动作只写可断言的隔离 sink,结束后按 scenario ID 清空,避免契约测试真的发信、扣款或污染下一例。

录制 fixture 时先在受控代理按字段 allowlist 脱敏,再写入 CAS;请求 Authorization、Cookie、自由文本和生产标识默认不保存。清单记录来源环境类别、录制工具、sanitizer 版本和删除字段计数,但不携带真实租户。动态数据用明确生成器替换,并保留类型、边界和字段间关系。过期时间不是自动失效依据:重新验证成功可延长认可窗口,失败则冻结 bundle 供旧版本回放。Agent 回放评测也依赖 fixture 血缘,但本文的接受者是 API 消费者,而非 Agent 决策轨迹。

用“合法但不等价”攻击门禁

故障套件注入未知枚举、nullable/absent 互换、金额从分变元、200 错误对象、重复游标、媒体类型变化、截断 JSON 和连接中断。再让 Mock 接受禁用字段、忽略 scope,并逐条 mutation normalizer:删除金额、折叠空值、重排数组或扩大 ignore path;同时令 producer 尝试写非隔离 sink。系统必须由独立 verifier 或业务 oracle 杀死这些变异,并定位 spec、producer、mock、normalizer 或 sink 边。

合并门要求:场景 schema 与必需错误分支覆盖率 100%;当前/上一组合全部通过;12 类故障、verifier/oracle/normalizer 变异杀死率 100%;未声明 ignore 为 0;Mock 与生产者的规范化摘要一致率 100%;隔离 sink 外写入和敏感字段泄漏为 0。概率接口必须声明指标、样本量和区间,不能用逐字相等制造假稳定。

发布先在 CI 双跑旧、新 contract set,只报告差异;随后让少数消费者显式选择新别名,最后提升默认 generation。若未知枚举错误增加、双跑差异超过批准基线或 consumer 失败率高于 1%,将别名 CAS 切回旧 bundle 与旧 Mock engine;新 bundle 保持不可变供诊断。迁移不能靠删除失败 snapshot,属性测试可补充边界生成,但仍必须绑定这里的 scenario 和 spec digest,才能证明测的是当前 API。

Related

相关推荐