契约测试是验证消费者期望与提供者实际接口行为一致性的测试方法,避免直接调用真实服务带来的网络、状态和依赖干扰,提升测试稳定性、速度与并行性。

什么是契约测试,为什么不用直接调用接口?
契约测试不是测服务能不能跑通,而是验证「消费者期望的接口行为」和「提供者实际暴露的行为」是否一致。直接调用真实服务会引入网络、状态、依赖等干扰,导致测试不稳定、慢、难并行。pytest 本身不内置契约测试能力,得靠 pytest-pact 或手写断言模拟契约逻辑——但更常见、更可控的做法是:用 Pact 工具生成 JSON 契约文件,再用 pytest 驱动验证流程。
如何用 pytest-pact 启动消费者端测试?
消费者测试本质是“我(客户端)发什么请求、期待什么响应”,不启动真实服务。关键点在于:Pact 会起一个 mock server 替代下游,pytest 调它的 client 发请求,然后自动比对是否符合预设契约。
- 安装:
pip install pytest-pact pact-python - 测试函数里必须用
@pact.verify()装饰器(注意不是@pytest.mark.pact) -
Pact实例需指定consumer和provider名,二者必须和后续 provider 验证时完全一致(大小写、空格都不能错) - mock server 默认绑定
127.0.0.1:5000,若端口被占,得显式传host和port参数
示例片段:
from pact import Pact
pact = Pact(consumer='OrderClient', provider='InventoryService')
@pact.verify()
def test_get_stock():
result = requests.get(f'http://127.0.0.1:5000/stock/{SKU_ID}')
assert result.json()['available'] == 10
provider 端验证时为何总报 “No interactions found”?
这是最常卡住的地方:provider 验证阶段需要读取消费者生成的 JSON 契约文件,并用真实服务响应其中定义的请求。报这个错,基本说明三件事没对上:
立即学习“Python免费学习笔记(深入)”;
- 契约文件路径不对——
pact-verifier默认找pacts/目录下的consumer-provider.json,文件名必须严格匹配 consumer/provider 名 + 小写+下划线 - provider 服务没真正收到 pact 发起的请求——检查服务是否监听在 pact 指定的
--provider-base-url,且路由、method、headers 完全一致(比如契约里定义了X-Auth-Token,你的服务却没解析它) - pytest 运行时没激活 pact 的 provider verifier:要用
pact-verifier --provider-base-url=http://localhost:8000 --pact-url=./pacts/orderclient-inventoryservice.json,不是跑pytest命令本身
如何避免契约漂移和版本混乱?
契约文件一旦提交到 Git,就代表双方约定。但没人手动维护它,很容易过期。关键控制点在 CI 流程里:
- 消费者测试通过后,自动生成新契约文件,但禁止直接覆盖旧版——用
pact-broker或至少按语义化版本存为orderclient-inventoryservice-v1.2.0.json - provider 验证必须针对「最新已发布契约」运行,而不是本地临时文件;否则会漏掉消费者新增字段
- 不要在
conftest.py里全局 patchrequests——这会让 pact 的 mock server 失效,所有请求直连真实服务,契约形同虚设
真正麻烦的是字段可选性:比如消费者只用了 id 和 name,契约里就得明确标 "optional": true,否则 provider 加个 description 字段就会失败。这点没法靠工具自动推断,得人工核对。


















