集成与契约测试
真实数据库、缓存、消息队列、HTTP/gRPC 与 MCP 边界的测试方法,以及 Consumer-driven Contract、Fixture、隔离和清理。
目录 · 21 节
- 1. 集成测试的范围
- 2. 为什么优先使用真实依赖
- 3. 数据库集成测试
- Transactional Outbox例子
- 隔离与清理
- 4. Redis集成测试
- 5. Kafka集成测试
- 6. HTTP与gRPC边界
- 7. Contract Test解决什么
- Schema兼容不等于语义兼容
- 8. MCP Contract Test
- Agent辅助Contract Testing
- 9. Fixture设计
- 10. Testcontainers与CI
- 11. Agent系统的真实集成层
- 12. 常见反模式
- 13. 面试速答
- 集成测试和E2E区别
- Contract Test能替代E2E吗
- Agent工具为什么要做Contract Test
- 参考
集成与契约测试
单元测试可以证明代码在假定的外部系统行为下能够工作;集成测试用于验证真实外部系统是否符合该假定。SQL语法、事务隔离、Redis TTL、Kafka Rebalance、HTTP Header和MCP Schema都可能在Fake实现与真实依赖之间存在差异。
1. 集成测试的范围
狭义集成测试一次验证一个边界:
Repository ↔ PostgreSQL
Cache Adapter ↔ Redis
Producer / Consumer ↔ Kafka
HTTP Client ↔ Fake or Test Provider
MCP Client ↔ Real MCP Server
广义集成测试可能启动一组服务。两种定义都可以使用,但报告必须明确SUT、真实依赖和替身,不能仅使用integration目录名称表示不同置信度的测试。
2. 为什么优先使用真实依赖
In-memory Fake通常无法忠实模拟:
- PostgreSQL MVCC、行锁、唯一约束、
SKIP LOCKED; - Redis Lua原子性、TTL、Eviction、Stream Consumer Group;
- Kafka Partition、Offset、Rebalance、事务Producer;
- HTTP Chunking、Header大小、代理Timeout;
- gRPC状态码、Deadline与Streaming Flow Control。
能在Container里快速启动的依赖,优先跑真实版本。Fake保留给单测中的错误注入和高频反馈。
3. 数据库集成测试
至少覆盖:
- Migration从空库能执行;
- Repository真实SQL能读写;
- Unique / Foreign Key / Check约束生效;
- 事务失败能Rollback;
- 并发更新、锁等待与隔离级别符合预期;
- Query Plan和索引满足高风险查询;
- 时区、NULL、Decimal、JSONB等类型正确映射。
Transactional Outbox例子
测试不能只断言业务表有数据:
BEGIN
INSERT incident ...
INSERT outbox(event_id, aggregate_id, payload) ...
COMMIT
应覆盖:
- 两条写入要么都提交,要么都回滚;
- Relay并发使用
FOR UPDATE SKIP LOCKED不重复认领; - 发布成功但标记失败时,重试产生重复消息,下游可幂等;
- Poison Message进入Dead Letter或人工处理,不无限热循环。
隔离与清理
常见策略:每Case事务回滚、每Case独立Schema、随机数据库名、Container per suite。选择取决于是否需要测试Commit后行为、连接池和跨事务并发。只靠DELETE FROM清理时要处理外键、Sequence和失败中断。
4. Redis集成测试
测试真实Redis时关注:
- TTL是否在预期时间范围,而不是精确到毫秒;
SET NX PX和Lua Compare-and-delete;- Stream Pending、ACK、Claim与重复投递;
- 序列化兼容和Key Namespace;
- Cache Miss、旧值、穿透与失效;
- Redis不可用时系统是Fail-open还是Fail-closed。
Fake Clock不能控制真实Redis时间。对TTL测试使用最终一致断言和合理Deadline,不要sleep(1s)后押宝调度器一生顺遂。
5. Kafka集成测试
Producer测试:Key路由、Header、压缩、重试、幂等、事务边界。Consumer测试:Deserialize、Offset提交、重复消息、乱序、Rebalance和Poison Record。
一个够用的场景:
Produce event_id=E1
→ Consumer处理业务写
→ 在Offset提交前模拟崩溃
→ 重启后再次收到E1
→ 业务唯一约束阻止重复副作用
→ Offset最终提交
这验证的是At-least-once现实,不是把enable.idempotence=true念成Exactly Once咒语。
6. HTTP与gRPC边界
Client Integration Test验证:
- URL、Method、Header和Body序列化;
- Status / gRPC Code到领域错误的映射;
- Timeout、Cancellation和Retry;
- Pagination、Streaming与大Body限制;
- Unknown Field与向后兼容;
- TLS、认证和Token刷新。
可以使用本地Fake Server精确模拟Malformed JSON、慢Header、不完整Body和连接重置。它比Mock一个Do(req)返回值更接近真实网络边界,但仍需使用Contract Test验证Fake与真实API契约一致。
7. Contract Test解决什么
Contract Test验证Consumer依赖的最小交互与Provider实际行为一致。Consumer-driven Contract流程:
Consumer用Mock Provider写期望
→ 生成Contract Artifact
→ 发布到Broker
→ Provider拉取Contract
→ 在指定Provider State下重放请求
→ Provider验证响应满足Consumer需求
→ 部署门禁判断版本能否安全组合
Contract不是完整业务测试。它证明请求响应形状、字段和交互满足约定,不证明Provider内部计算正确,也不证明两个服务在生产配置下完整工作。
Schema兼容不等于语义兼容
Provider新增Optional字段通常兼容;删除Consumer正在读取的字段会破坏Contract。更隐蔽的变化包括:字段仍为String,但从UTC时间改为本地时间;状态码含义变化;排序从稳定变为随机。Contract中应表达Consumer实际依赖的语义,避免约束无关字段。
8. MCP Contract Test
MCP边界至少验证:
- 支持的Protocol Version和Capability;
tools/list中名称、Description、inputSchema、outputSchema;tools/call成功Result、isError和JSON-RPC Error;- Malformed、Unknown Tool、Missing Scope、Approval Required;
- stdio换行Framing与stdout纯协议;
- Streamable HTTP的Content-Type、版本Header、SSE消息边界;
- Result大小、Resource Link、脱敏和Trace字段。
Tool Schema可生成Hash并放进Contract Artifact。Server升级Schema时,Consumer CI用真实Server验证旧Case;Host也要测试未知Tool和新增字段不会导致崩溃。
Agent辅助Contract Testing
Agent可以读取OpenAPI、Proto、MCP Tool Schema和历史Trace,生成候选边界Case、识别Consumer真正读取的字段、解释Breaking Change。但生成结果必须经过:
Schema校验
→ 人工确认业务语义
→ Provider真实验证
→ 固化为版本化Contract
不能仅依据模型对文档的分析结果判定兼容性,最终结论必须由真实Provider验证和版本化Contract支持。
9. Fixture设计
Fixture应最小、显式、可组合:
Tenant A
User admin
Incident running
Tool approval pending
Tenant B
不可见A的数据
Builder提供默认值,Case只覆盖关心字段。Fixture必须版本化;时间、ID和Hash应稳定;涉及PII使用合成数据。测试失败时保留必要日志和Container状态,成功后清理。
10. Testcontainers与CI
Container化依赖让本地和CI运行相近版本。注意:
- 固定Major/Minor镜像,不用
latest; - 等待Readiness,不是端口刚开就开测;
- 容器、网络和Volume有唯一名称;
- 并行Case避免共享Topic、DB和Consumer Group;
- 缓存镜像但不缓存脏数据;
- CI失败上传日志、Schema、Offsets和Trace。
11. Agent系统的真实集成层
可把模型保持Fake,先验证真实基础设施:
Fake Model固定选择 query_logs
→ Real MCP Client
→ Real Log MCP Server
→ Test Loki / HTTP Stub
→ Real Trace Store
再单独用真实模型连接Fake Tool评测Tool Selection。把“模型变量”和“基础设施变量”拆开,失败时才知道该找Prompt、协议还是数据库。
高风险Tool还要验证Policy与Approval是Server-side Enforcement,不是Prompt里一句“请谨慎”。
12. 常见反模式
- 所有服务一起启动,任何失败都叫Integration Failed;
- 用H2/SQLite代替PostgreSQL却断言数据库兼容;
- 测试直接打生产第三方API;
- Fixture跨Case共享并依赖顺序;
- Retry把真实失败刷成绿色;
- Mock契约从不向真实Provider验证;
- 清理失败后污染后续Case。
13. 面试速答
集成测试和E2E区别
集成测试通常聚焦一个或少数边界,允许内部控制和替身;E2E从外部入口走完整系统与关键旅程。
Contract Test能替代E2E吗
不能。它减少跨服务接口不兼容,但不验证完整部署、路由、配置和用户旅程。
Agent工具为什么要做Contract Test
模型和Host依赖Tool Schema、错误类型及结构化Result。契约漂移可能表现为工具选择或参数生成质量下降,实际原因是协议发生了不兼容变化。