ChatGPT、Codex工程实战:API测试全部通过,为什么下游服务上线后还是被改挂了?

发布时间:2026/10/11 5:38:58
ChatGPT、Codex工程实战:API测试全部通过,为什么下游服务上线后还是被改挂了?
最近用 ChatGPT、Codex 改公共接口时我越来越警惕一种特别容易产生“假安全感”的场景Provider自己的测试全部通过不代表下游Consumer还能正常工作。比如一个用户接口原来返回userId是数字status只有ACTIVE / DISABLED两种状态。这次为了适配新系统Codex把userId改成字符串又增加了一个新的PENDING状态。Provider这边看起来一切正常。单元测试通过。Integration Test通过。接口返回200。Schema校验也没问题。于是PR顺利合并。结果上线没多久下游两个老服务开始报错。一个服务反序列化userId失败。另一个服务遇到PENDING后直接进入默认异常分支。这时候最容易问API测试明明全绿为什么还能把下游改挂因为真正漏掉的是Provider验证的是“我还能正常提供接口”Consumer关心的是“这个接口是不是还符合我原来依赖的那些假设”。这两个问题并不完全一样。一、先给核心判断API正确不等于API兼容这是公共接口修改里一个特别关键的区别。假设新接口设计得完全合理。字段命名更清楚。类型更统一。新的状态模型也更完整。从Provider角度看这次修改甚至可能是一次优化。但下游Consumer并不关心新设计是不是更漂亮。它真正关心的是我昨天还能正常调用的接口今天还能不能按原来的方式继续工作。所以公共API至少存在两种正确性Functional Correctness接口本身能不能正确执行。以及Compatibility Correctness已有Consumer还能不能继续使用。很多测试只覆盖了第一种。真正的线上事故却发生在第二种。二、Provider自己的测试为什么天然容易漏掉Consumer假设因为Provider测试通常站在Provider视角写。比如输入合法参数。返回正确字段。错误时返回正确状态码。数据库写入正确。业务逻辑正确。这些都没问题。但Consumer可能偷偷依赖了一些Provider根本不知道的细节。例如字段永远不是null。数组顺序固定。枚举永远只有三个值。某个字段虽然文档说可选但线上过去三年一直存在。时间格式固定带毫秒。错误码虽然是字符串但下游直接硬编码比较。这些东西不一定写进正式Contract。但真实Consumer已经依赖了。所以接口升级最危险的地方经常不是显式Contract被改了。而是隐含Contract被破坏了。三、最典型的坑字段“还在”下游照样可能挂很多人判断Breaking Change时只看字段删没删。接口路径改没改。方法还在不在。其实远远不够。例如字段仍然叫userId但类型从Number变成String。Provider测试当然可以全部通过。新的客户端也能正常使用。但旧Consumer如果严格按Long反序列化就会直接失败。类似的还有null→ 非null。非null → 允许null。单值 → 数组。秒级时间戳 → 毫秒级时间戳。字符串日期 → ISO 8601。这些变化从“字段存在性”来看都很小。从Consumer角度却可能是完全不同的Contract。四、枚举新增为什么也可能是Breaking Change这个特别容易被忽略。假设接口原来只有SUCCESSFAILED两个状态。Provider新增一个PROCESSING从设计角度看非常合理。而且旧字段、旧接口一个都没删。于是很容易判断这是向后兼容的新增。但如果旧Consumer代码写成SUCCESS走成功。其他全部当失败。那PROCESSING一出现业务语义就错了。更极端一点如果下游反序列化库配置成遇到未知枚举直接报错那服务甚至会直接异常。所以“只新增不删除”也不能简单等于一定兼容。真正要问的是已有Consumer面对新值时会怎么表现。五、Mock为什么特别容易制造“下游没问题”的错觉有些团队会说我们下游也有测试而且都绿。继续看以后却发现Consumer测试里的Provider响应全是自己Mock出来的。比如Mock一直返回旧字段结构。旧枚举。旧错误码。所以Provider已经变化了Consumer测试仍然在测试一个已经不存在的旧世界。这也是Mock最危险的一面。Mock本身没错。但如果它长期不跟真实Provider Contract同步就会变成假安全感制造器。于是你会看到Provider CI绿。Consumer CI也绿。真正集成以后挂了。六、所以公共API真正需要的是“跨边界验证”如果一个API只有一个团队内部使用风险还比较可控。但一旦它被多个微服务。多个客户端。多个团队长期依赖就不能只靠Provider自己的测试。更完整的验证应该回答关键Consumer是不是仍然能接受当前Contract这就进入Contract Testing。其中一种常见思路就是Consumer-Driven Contract。它不是让Provider猜“下游可能需要什么”。而是让Consumer明确告诉Provider我实际依赖的是这些行为。例如我需要userId是可解析的数字。我需要status至少支持这些值。我需要404时返回特定错误结构。然后Provider每次改接口都验证这些关键Consumer Contract还能不能满足。七、Consumer-Driven Contract真正解决的不是“更多测试”它解决的是谁来定义兼容性。传统Provider测试经常是Provider自己定义“什么叫正确”。而Consumer-Driven Contract多了一层Consumer定义“我真正依赖你什么。”这样接口变化以后不需要等测试环境联调。灰度。生产报错才发现兼容问题。而是在PR阶段就能知道某个Consumer Contract已经被破坏。这特别适合ChatGPT、Codex开始频繁改API以后使用。因为Agent改代码很快。如果缺少跨服务约束它也会更快地制造局部正确、系统不兼容。八、Agent改公共API前不应该第一步就改Schema现在如果让Codex修改一个公共接口我更希望它先做Consumer Discovery。也就是先找谁在调用这个API哪些内部服务依赖它有没有SDK有没有移动端、Web端有没有异步任务有没有脚本或低频工具如果只扫描Provider仓库很容易得到一个错误结论这里改起来影响不大。因为真正的影响根本不在这个仓库里。所以公共API改动前Dependency Map和Consumer Map非常重要。九、一个接口修改真正该问哪几个问题我现在更关注四件事。第一Shape有没有变化字段、类型、结构、nullability。第二Semantic有没有变化同一个字段含义是否变化。第三Value Space有没有变化枚举、错误码、状态值有没有新增或收缩。第四Consumer Assumption有没有变化旧Consumer原来成立的假设是不是还成立。这四层比单纯看“OpenAPI Diff里有没有删除字段”更接近真实兼容风险。十、为什么接口版本化也不是万能解法有人会说那每次Breaking Change就直接发v2。当然可以。但如果每一个小变化都新开版本很快就会出现v1。v2。v3。然后多个版本长期共存。维护成本会越来越高。真正好的API演进应该尽量做到能兼容扩展时不急着Break。例如新增可选字段。双字段过渡。新增枚举时让Consumer具备Unknown处理能力。先扩展再迁移最后收缩。只有真正无法兼容时再进入明确的版本切换。这和前面讲过的多仓库升级其实是同一类思想迁移过程本身也要被设计。十一、API测试全部通过为什么还要验证老Consumer因为Provider测试验证的是现在这个版本自己能不能工作。老Consumer验证的是昨天的使用方式今天还能不能继续成立。特别是长期运行的系统里真正危险的是Provider已经升级。但Consumer没有同步升级。这种 N / N-1 共存状态一定会存在。所以公共接口真正值得验证的不只是新Provider 新Consumer。还要至少考虑新Provider 旧Consumer。如果这个组合完全没测那你上线的其实不是一个接口修改。而是一场未经验证的兼容性实验。十二、我更推荐一套Agent修改API的顺序现在让ChatGPT、Codex处理公共API时我会更倾向于Identify Consumers → Extract Current Contract → Identify Breaking Surface → Design Compatible Change → Update Provider → Validate Key Consumers → Gradual Rollout → Cleanup也就是先找Consumer。再确认现有Contract。再判断哪些地方会Break。尽量设计兼容变化。最后才改Provider。这和先改Schema → 修编译 → 跑测试最大的区别在于前者把系统当成一组互相依赖的服务。后者只把当前仓库当成任务边界。十三、一个主指标Consumer Compatibility Coverage这篇我建议只留一个指标Consumer Compatibility Coverage——消费者兼容覆盖率可以简单理解为已经完成兼容验证的关键Consumer数量 ÷ 本次API变更涉及的关键Consumer总数比如一个公共API有5个重要Consumer。目前只验证了新版SDK。一个新服务。另外3个旧Consumer完全没跑。那即使Provider测试100%通过Consumer Compatibility Coverage也只有40%。这个指标最大的意义就是强迫团队问我们到底验证了多少真实调用方而不是只问“Provider CI绿了吗”十四、什么时候这个PR不应该直接合并我会特别警惕几类情况API类型变化但没有验证旧Consumer。新增枚举值但下游没有Unknown策略。nullability发生变化。错误结构发生变化。SDK重新生成但旧SDK仍在线上大量使用。Provider测试全绿但没有任何跨服务Contract Test。明知道存在多个Consumer却根本不清楚谁还在调用。这时候继续合并本质上是在把兼容性风险推迟到上线以后。十五、ChatGPT、Codex在这里真正适合做什么不要只让它“帮我把API改一下。”可以先让它做Consumer扫描。Schema Diff。Breaking Change分析。旧SDK调用分析。Contract风险分类。关键Consumer测试补全。如果多个仓库都能读取Codex还可以进一步对比Provider改动和Consumer实际解析逻辑。这时候Agent的价值就不只是把接口改得更快。而是提前发现“这个变化会把谁改挂”。十六、Plus和Pro怎么判断如果你主要让ChatGPT、Codex处理单个API。少量Consumer。局部Schema修改。偶尔分析一次兼容问题。这种任务规模下Plus通常已经够用。如果你的日常已经变成大型微服务体系。一个公共API有多个Consumer。一次修改需要跨Repo分析SDK、Contract、测试、发布顺序和灰度兼容。还要让Codex连续读取多个仓库、修改代码并验证不同版本组合这种长上下文、多仓库、多阶段的Agent Workflow越来越频繁时Pro会更适合。真正的判断标准不是API多复杂。而是ChatGPT、Codex是不是已经持续参与完整的跨服务兼容性验证。最后API测试全部通过为什么下游服务上线后还是会被改挂因为Provider正确不代表Consumer兼容。真正容易被漏掉的是字段类型变化。Nullability。枚举扩展。错误结构。隐含业务假设。以及旧Consumer还没有升级。所以公共API修改以后真正该验证的不只是“接口还能不能正常返回”。还要继续问“昨天还在用这个接口的Consumer今天还能不能继续正常工作”更完整的Agent工作流应该是Consumer → Contract → Breaking Surface → Compatible Change → Provider → Consumer Validation → Rollout当ChatGPT、Codex开始越来越快地修改公共API以后人真正需要守住的也不只是接口有没有写对。而是这个接口进入真实系统以后原来的消费者还能不能安全活下来。