SuperAPI AI 工具集

contract-tests

测试与质量

查看项目

更新于 10-29 13:22

技能介绍

契约测试(Contract Tests)用来表达这样一条承诺:这些字段、这些类型,是向后端消费者保证的。增加字段没有问题;但删除、重命名或更改已承诺字段的类型,测试就会失败,且失败信息会说明谁会受影响。

一、用途与目标 它的核心用途是保护其他仓库依赖的 API 响应,以及 webhook、队列等载荷:让一个会破坏消费方的后端变更在 CI 阶段就失败,而不是等到上线后才发现。

二、主要能力与使用方式

第 1 步:找出契约 从仓库映射(repo map)的 Connections 表和影响分析中,列出被其他仓库消费的端点和载荷。对每个端点,通过搜索消费者仓库中的字段名,确认消费者实际读取了哪些字段。契约是消费者实际使用的内容,而不是端点返回的全部内容。

第 2 步:选择实现方式 先检查项目已有条件,选择最轻量且合适的一种。

A. OpenAPI 模式对比(项目使用 drf-spectacular 或 drf-yasg 时) 在 CI 中生成模式,并与已提交的副本比较,对破坏性变更报错:

python manage.py spectacular --file openapi.yml --validate git diff --exit-code openapi.yml # 或使用 oasdiff 只检查破坏性变更:oasdiff breaking old.yml openapi.yml --fail-on ERR

这种方式投入小、覆盖广,但准确度取决于序列化器的注解。

B. 在提供方仓库编写 Pytest 契约测试(默认方式) 每个被消费的资源对应一个测试模块,放在 tests/contracts/ 下,例如:

tests/contracts/test_invoice_contract.py

Consumers: web-app (src/api/invoices.ts), reporting-service (exports/invoices.py)

INVOICE_CONTRACT = { "id": int, "number": str, "status": str, "total": str, # decimal serialized as string "issued_at": str, # ISO 8601 "customer": dict, } INVOICE_STATUSES = {"draft", "issued", "paid", "void"}

@pytest.mark.django_db def test_invoice_detail_contract(api_client, invoice): body = api_client.get(f"/api/invoices/{invoice.id}/").json() for field, typ in INVOICE_CONTRACT.items(): assert field in body, f"'{field}' removed; consumers: web-app, reporting-service" assert isinstance(body[field], typ), f"'{field}' changed type" assert body["status"] in INVOICE_STATUSES

覆盖范围包括:详情、列表(含分页信封 count、next、results)、错误格式(400 响应体结构),以及消费者用于分支判断的枚举值。可空字段应显式断言 typ | None,这样字段转为可空就成为一个明确的决策。

C. 消费者驱动契约(Pact) 仅当团队已经在运行 Pact broker 时使用,不要为了一个端点引入它。

Webhook / 队列载荷:做法与 B 相同,但要用真实的生产者函数构建载荷,再断言其结构。

第 3 步:接入 CI 确保契约测试在提供方仓库的常规测试命令中运行(它们本身就是 pytest)。可选但有用的做法:在消费者仓库中写一段简短说明,指向提供方的契约文件,让消费者开发者知道该在哪里添加他们新依赖的字段。

第 4 步:变更契约 当契约测试失败时,这正是它的意义所在。要么这次变更本身是错误,要么是有意变更,此时应走安全变更与跨仓库变更流程:先扩展(把新字段加入契约),消费者切换,然后收缩契约(在从 API 移除旧字段的同一个 PR 中,把该字段从契约里移除)。

三、规则 每个契约文件都应写明其消费者,以及字段在消费者代码中被读取的位置。 不得在并非明确契约变更的 PR 中,通过删除断言来“修复”失败的契约测试。 契约应只保留消费者使用的字段;若断言每个字段,任何变更都会变成契约变更,人们就会开始忽视失败。

四、适用场景与限制 适用于多个仓库共享后端接口、webhook 或队列载荷的场景,尤其是需要防止破坏性变更影响下游消费方时。局限在于:OpenAPI 方式依赖序列化器注解的准确性;Pact 需要已有 broker;契约范围应控制在消费者实际使用的字段。