4. 兼容性测试策略
兼容性测试,到底测什么?
说白了,就这四样:前向兼容、后向兼容、API兼容性、行为兼容性。光测一个不够,全得覆盖到。我见过太多团队只盯着「新功能能用」,结果老业务崩了,用户骂街。
核心原则:后向兼容是底线,API兼容是契约,行为兼容是信誉,前向兼容是远见。
4.1 前向兼容 vs 后向兼容
这两个概念,刚入行的兄弟容易搞混。我给你捋一捋:
| 维度 | 前向兼容 | 后向兼容 |
|---|---|---|
| 定义 | 新版本SDK写的代码,能在旧版本SDK上运行 | 旧版本SDK写的代码,能在新版本SDK上正常运行 |
| 关注点 | 新特性降级、API版本感知 | 废弃API、数据结构变更 |
| 测试方向 | 旧环境跑新代码 | 新环境跑旧代码 |
| 典型问题 | 调用不存在的API、格式不兼容 | 返回字段缺失、行为突变 |
我记得刚入行那会儿,做支付SDK。新版本加了个加密字段,结果老版本根本没这个字段。我没有做降级处理,直接让老SDK的APP调用新接口,崩了。你说冤不冤?前向兼容,就是要你考虑「用户不升级」的情况。
小技巧:写SDK时,新API加标记,比如
@since(2.0.0)。运行时检查版本号,低于某个版本就走降级逻辑。
后向兼容呢,更常见。你升级了SDK,老用户不更新你的库,但他调你的API。你不能改响应结构,不能删字段。我有个同事,新版本把返回的 status 字段名改成了 state,结果线上很多老代码直接用 response.status ——全挂了。你说这算不算低级错误?
4.2 API兼容性
API兼容性,是SDK和调用方之间的契约。
一旦对外发布,接口签名就是承诺。举个例子:
// v1.0.0 - 原版
public User getUser(int id)
// v2.0.0 - 改了参数类型,不兼容
public User getUser(String id)
// 调用方按int传参,编译都过不了,更别说运行了
API兼容性需要关注以下几个维度:
- 方法签名不变:方法名、参数类型、返回类型不能变。
- 异常类型不变:新增异常可以,但不能删除已有异常。
- 字段不能删:JSON响应里,之前有
errorMsg,新版本不能去掉。 - 枚举值不能砍:状态码、枚举常量只能增不能减。
警告:签名兼容 ≠ 行为兼容。接口没变,但内部逻辑变了,这才是隐藏最深的坑。我亲历过一次,接口返回格式没变,但排序规则改了,前端没发现,上线后数据对不上,排查了一整天。
怎么测API兼容性?我的习惯是这样的:
- 把旧版本SDK和调用方代码打包成测试项目。
- 用新版本SDK替换旧SDK的jar/aar包。
- 不修改调用方代码,直接运行已有测试用例。
- 如果编译通过、测试全绿,API兼容性就算过了。
4.3 行为兼容性
行为兼容性,是最容易被忽略的。
你想想看,API没变,返回值格式没变,但——
- 同一个操作,以前返回空字符串,现在返回null
- 以前是同步的,现在改成了异步
- 以前异常是
IOException,现在变成了RuntimeException
这些都属于行为不兼容。你说接口没坏吧?调用方逻辑完全可能炸。
有一次我们升级了底层图像算法,接口还是 Image process(Image input),返回的也是 Image。但新版本对某些角度的图片处理耗时从50ms飙到了500ms。调用方没设超时,结果整个APP卡死。你说这是API问题吗?不是。是行为问题。
行为兼容性测试,要盯的是:
| 检查点 | 说明 |
|---|---|
| 返回值语义 | null vs 空对象、顺序是否一致、默认值是否不变 |
| 异常语义 | 异常类型、错误码、异常消息格式 |
| 性能特征 | 响应时间是否退化、内存消耗是否剧增 |
| 副作用 | 写文件、发网络请求、改全局状态等行为是否新增或减少 |
| 并发行为 | 锁粒度变化、线程模型变化 |
测行为兼容性,光靠单元测试不够。我建议你拿老版本的测试用例,直接在线上切换前后对比看差异。或者用diff工具比较两个版本的完整调用日志。我曾经手工跑了一百多个回归用例,就为了确认同一个输入,输出是否完全一致。
我这里有个检查清单:每次回归测试,把这四点过一遍——前向兼容、后向兼容、API兼容、行为兼容。少一个,线上事故风险就高一分。
个人建议:如果你的SDK有百万以上用户量,行为兼容性必须加自动化回归流程。光靠人肉测,迟早出问题。