摘要:Google与Speakeasy开放OpenAPI生成工具后,本文聚焦生成式SDK的质量门:同一规范在不同语言里必须保持序列化、错误、流式取消和版本来源一致。
9月17日,Google宣布与Speakeasy合作开放OpenAPI代码生成套件。它可以生成多语言SDK,支持严格类型、SSE流式能力,还能产出面向Agent的CLI和文档MCP服务。
生成代码变得容易以后,测试问题反而更尖锐:Python客户端把缺省字段省略,Java客户端把它序列化成null,服务端会不会把两次请求理解成两种业务意图?
“都能调用成功”不是兼容性结论。真正要证明的是不同语言对同一个契约做出了相同解释。
建一份语言无关的黄金向量
不要为每种SDK各写一套随意的测试。先定义一组与语言无关的输入、HTTP请求和业务结果:
vectors = [
{
"name": "omit_optional", "input": {
"order_id": "A1"},
"expected_json": {
"order_id": "A1"}},
{
"name": "explicit_null", "input": {
"order_id": "A1", "coupon": None},
"expected_json": {
"order_id": "A1", "coupon": None}},
]
def assert_wire_equal(actual, expected):
assert actual == expected
每种语言都读取同一份向量,把请求发给记录型Mock Server。比较的不是对象长得像不像,而是最终method、path、header、body、重试和超时是否一致。
SSE最容易藏住“看起来能用”的差异
流式接口至少要测四个动作:首事件延迟、事件顺序、客户端取消、断线重连。某个SDK能收到完整答案,不代表用户点击停止后连接真的关闭;某个SDK自动重连,也可能重复消费最后一条事件。
回归用例要记录event id,并断言重连后不重复提交副作用。取消后还要观察服务端是否收到断开信号,而不是只看界面不再刷新。
生成器升级也要进入变更半径
规范没改,生成器版本变了,产物依然可能变化。因此发布物必须同时记录OpenAPI摘要、生成器版本、模板版本和语言运行时版本。CI中做两类Diff:公开API签名Diff和线上报文Diff。
前者告诉开发者“调用方式变了”,后者告诉测试者“业务语义变了”。只有格式变化且报文、错误和流式行为保持一致时,才可以低风险合并。
错误契约比成功契约更容易分叉
很多团队的跨语言用例只测200响应。真实用户最容易感受到差异的,反而是失败路径:Python抛出RateLimitError并暴露retry_after,Java只给一个通用异常;一个SDK遇到429自动重试三次,另一个直接失败;一个保留服务端request_id,另一个把它丢了。
黄金向量因此要同时定义HTTP状态、业务错误码、可重试性、最大重试次数和最终异常类型。不要强求不同语言的类名相同,但要保证调用者可以做出相同业务决策。
一条可落地的CI流水线
每次OpenAPI或生成器变化时,可以按以下顺序运行:
- 校验规范本身能否解析,并检查破坏性Schema变化;
- 在固定容器中生成各语言SDK,记录生成器和模板版本;
- 编译所有产物,运行语言无关黄金向量;
- 对成功、失败、超时、取消分别采集线上报文;
- 比较上一稳定版本的公开API和业务行为;
- 只有允许清单内的差异才能进入发布。
这里要特别防止“重新生成后Git Diff太大,没人愿意审”。可以把格式化、注释时间戳和文件顺序从语义Diff里剥离,只把公开方法、参数默认值、请求报文和错误映射推给评审者。
最后再做一次消费者测试:让一个真实示例应用分别升级各语言SDK,确认旧调用能编译、核心流程能跑。生成器自己的测试通过,不等于下游应用没有被破坏。
开源解决的是可持续获得工具的问题,不自动解决生成结果的正确性。测试团队最值得补的不是第七种语言脚本,而是一套所有语言共用的契约向量。它能在SDK生成、Agent CLI和MCP文档服务之间,守住同一个业务事实。