Files
lixi 222990e587
CI / docs-build (push) Successful in 1m19s
docs: M2 第二波收口——报告 13~21 与契约草案入档挂导航
- 13~18 后端纵切六单报告(T2-03~08,测试 95→182)
- 14 + openapi-pets-draft.yaml 契约起草档案
- 19 契约冻结报告(v1.2.0,22 项草案修正对照)
- 20 契约一致性测试(快照机制 + 1 漂移修复)
- 21 第二波收口总表(定型语义汇总,第三波接入依据)
- mkdocs build --strict 通过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 10:40:27 +08:00

7.3 KiB
Raw Permalink Blame History

20 · T2-09 契约测试报告:实现与冻结契约 v1.2.0 的一致性保障

  • 日期2026-09-08
  • 角色Senior Developer(后端)
  • 工单T2-09 验收的契约一致性保障
  • 代码提交patbond-api dev d026f2f(基线 00f7dbd
  • 结论:pets 域 18 操作全矩阵契约测试落地并入 CI(./mvnw test 即自动执行,ci.yml 零改动);发现并修复漂移 1 项;全套 ./mvnw clean test 182 项全绿171 → 182+11)。

1. 机制选型:冻结快照进测试资源

选定方案:把 doc 仓正典 docs/api/openapi.yamlv1.2.0,冻结于 doc main@511617b字节级复制为 patbond-api 测试资源 patbond-pet/src/test/resources/contract/openapi-v1.2.0.yaml,契约测试对照快照跑。复制时点双方 sha256 均为 243fe648…4a4cd689d

否决的备选CI 里 checkout doc 仓再喂给测试。现有 ci.yml 是零外部 action、手动 git init + fetch 克隆本 Gitea 实例的模式,跨仓 checkout 意味着在工作流里再造一段带 token 的手动克隆、并让本地 ./mvnw test 依赖兄弟目录存在——本地与 CI 行为分叉,违背「门禁与本地同一条命令」的既定纪律。快照方案零 CI 改动、本地 CI 完全同构,代价只是一条同步纪律(见 §1.2)。

解析与校验实现:不引 swagger-parser / openapi-validator 类库——快照只用到 OpenAPI 3.0 的一个小子集(本地 $ref、type/required/nullable/enum/format/min-max),用构建里已有的 snakeyaml(Boot 传递依赖)解析 + 自写严格断言(约 500 行测试代码),零新增 Maven 依赖。自写的关键收益:未声明字段即报漂移——标准 OpenAPI 语义默认允许 additionalProperties,而冻结契约的语义是「恰好这些字段」,现成校验器恰恰放过改名/新增泄漏字段这类最常见漂移。

1.1 三个测试类

文件(均在 patbond-pet/src/test/java/...pet/contract/ 职责
OpenApiContract 加载快照、解析本地 $ref、枚举操作/状态码/schema
ContractValidator 响应体对 schema 严格校验:必填缺失、null 无 nullable、契约未声明的字段、类型/枚举/uuid/date-time/date 格式、min/max(Length) 边界
ContractConformanceTest 沿用既有 Testcontainers + MockMvc 基建真实起服务,18 操作逐一发请求校验,最后两个门禁测试(见 §2)

1.2 快照同步纪律

  1. 正典唯一:契约的唯一权威是 doc 仓 docs/api/openapi.yamlapi 仓快照是冻结副本,永不单独修改
  2. 契约变更流程:doc 仓升版(如 1.3.0)→ 复制新文件为 src/test/resources/contract/openapi-v1.3.0.yaml(删旧快照)→ 更新 OpenApiContract.RESOURCE 与守卫测试期望值(版本号、路径/操作/schema 数)→ 按新契约增删测试用例,一并提交。
  3. 忘同步的兜底:守卫测试 frozenSnapshotIsTheExpectedContractVersion 锁定 info.version == 1.2.0 且 18 路径 / 24 操作 / 45 schema——契约变更后只改快照不改测试(或反之)都会在 CI 立即变红,不会默默对着旧契约测试。

2. 测试什么:全响应矩阵 + 双门禁

覆盖 pets 域 18 个操作(契约中 tags ∈ {pets, dictionaries, health-records} 的全部操作,恰为 v1.2.0 新冻结的 12 路径)。每个操作真实发请求,对契约声明的每一个 (操作, 状态码) 单元格做结构校验:

  • 成功形态(6 个用例):宠物 CRUD 全字段/全空两种形态、品种与疫苗目录(含 species 过滤)、体重与健康事件的 cursor 分页翻页(并断言 hasMore=true ⇒ nextCursor 非空hasMore=false ⇒ nextCursor 恒 null)、疫苗 scheduled/completed 两形态与状态机 PATCH、提醒 completed/dismissed 两种流转、摘要空档案(三聚合 null)与满档案(四聚合非 null)+ tz 参数。
  • 错误信封(3 个用例):18 操作逐一裸请求验 401/4010111 个 pet 路径操作验 40401 防枚举、3 个顶层短路径验 40402、8 个写操作按 viewer/caregiver 角色验 4030012 处 400/40000(缺必填、limit 越界、非法 cursor、非法 species/status/tz)、40902 乐观锁过期(pets/vaccinations/health-events 三处)、40903 芯片号冲突、40904 剂次冲突、42201 疫苗规则两形态、42202 提醒规则。
  • 门禁一(快照守卫):见 §1.2 第 3 条。
  • 门禁二(覆盖率自证):everyDeclaredResponseCellIsExercised 断言上述用例真实触发并通过校验了契约声明的每一个响应单元格——契约将来新增操作或状态码,此测试自动变红,覆盖不会静默滑坡。唯一豁免PATCH /care-reminders/{id} 的 409(无 version 列,靠并发条件更新守卫落空触发,单线程 MockMvc 无法确定性构造;其行为语义由第一波并发一致性设计与集成测试背书)。

行为语义(状态机迁移合法性、防枚举响应一致性、权限矩阵、幂等键语义)不在本单重复——既有 78 项 pet 集成测试已锁定,本单只锁结构

有效性自证(mutation check,未入库):向快照 Pet schema 注入假必填字段 bogusDriftField 后跑测试,9/11 用例即刻红($.data.bogusDriftField: 契约必填字段缺失);还原快照后全绿。校验器确实在咬合,不是恒真。

3. 发现并修复的漂移

# 位置 契约 实现(修复前) 定性与处理
1 POST /api/v1/pets 请求体 sex CreatePetRequest.requiredsex sex 可缺席,服务端静默补 unknown 结构性漂移,按「以冻结契约为准」修实现:CreatePetRequest.sex@NotBlank(缺失 400/40000),PetService 移除缺省补值;7 个既有测试文件的创建载荷补 sex 字段

仅此 1 项。其余 17 个操作的请求必填、响应字段名/类型/nullable、错误码值与冻结契约零偏差——第二波「先定型实测行为、再按行为冻结契约」的流程有效。无语义级冲突,无需仲裁项。

4. 测试数变化

模块 之前 之后 变化
patbond-common 3 3
patbond-user 59 59
patbond-auth 31 31
patbond-pet 78 89 +11ContractConformanceTest6 成功形态 + 3 错误信封 + 2 门禁)
合计 171 182 +11

JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean testBUILD SUCCESS182 项 0 失败。CI 无需任何改动——契约测试就是普通 surefire 测试,./mvnw -B clean test 门禁自动携带。

5. 范围外记录

  • auth 域 6 操作无契约测试register/login/refresh/logout/me/trackEvents):M1 交付时无此机制,本单按工单口径不补,建议 M2 内另立工单——机制已就绪(快照已含 auth 域全部 schemaOpenApiContract/ContractValidator 直接复用),估计半天以内,落在 patbond-auth 与 patbond-user 的测试模块。
  • 提醒 PATCH 409 豁免:如后续想消除唯一豁免,可在测试中直接 UPDATE 数据库把提醒改成终态后再以旧状态提交 PATCH,确定性触发守卫落空;本单未做(属行为构造技巧,优先级低)。