回归测试是什么?我的理解
先聊个我踩过的坑。几年前维护一个Kotlin写的后端服务,每次加新功能都只测新增的接口。某个周五改了个工具类里的空指针判断,结果周一线上挂了一片——依赖这个工具类的十几个老接口全炸了。那之后我才真正意识到:回归测试不是锦上添花,是项目的刹车片。
说白了,回归测试就是每次代码变动后,把老功能重新跑一遍。你不用每次手动点,写个自动化测试就行。在Kotlin生态里,官方提供的kotlin.test库就是干这个的。
定义:别把回归测试想得太复杂
回归测试的核心就一句话:保证改过代码后,以前能跑的还能跑。它不关心新功能的正确性(那是功能测试的事),而是盯着旧功能有没有被无意破坏。
举个例子:你重构了某个类的异步协程逻辑,回归测试会检查原来的同步调用、异常处理、数据一致性是否依然正常。我把它叫作“安”。你想想看,没有这张网,谁敢在线上频繁发布?
回归测试 ≠ 全部重测 我们不是把上千个用例全跑一遍。好的回归测试是“精准打击”——只验证受到变更影响的模块和边界。
目的:我们为什么要做回归测试?
- 捕捉增量风险:小改动可能引发大问题,尤其是公共模块、基类、扩展函数这些“牵一发动全身”的地方。
- 加速迭代节奏:有回归测试保驾护航,你才敢大胆重构。我记得有个项目,加回归测试后发布频率从两周一次变成一天三次。
- 沉淀测试资产:每次发现的bug,补一个回归用例,项目就能越来越稳。说白了,测试用例就是项目的“免疫疫苗”。
重要性:没有回归测试,项目迟早翻车
我曾经在接手一个老项目时,发现一个线上bug改了三周——因为每次改完,总会有新的地方崩掉。后来我和团队花了三天补了一百多个回归用例,之后同样的改动只需半天。
回归测试的重要性可以用表格对比说明:
| 维度 | 有回归测试 | 无回归测试 |
|---|---|---|
| 代码变更信心 | 高,push前跑一遍就知道是否安全 | 低,改一行代码都要手动点所有关联页面 |
| 上线错误率 | 通常 < 2% | 可能达到 10%~30% |
| 新人上手速度 | 快,回归用例就是活文档 | 慢,全靠口口相传 |
| 维护成本 | 前期投入,后期节省大量排查时间 | 后期被动增加,每次出bug都要从头查 |
你看到差距了吧?有人觉得写测试浪费时间,其实恰恰相反——写过一次,后续每一版都能白嫖那个“安”。
在Kotlin中使用回归测试:一个极简例子
你不用等全部写好再跑。比如我们有个计算订单总额的方法:
// 业务代码
fun calculateTotal(items: List<Item>): Double {
return items.sumOf { it.price * it.quantity * (1 - it.discount) }
}
// 回归测试(使用kotlin.test)
class OrderTest {
@Test
fun `回归验证:空列表应返回0`() {
val result = calculateTotal(emptyList())
assertEquals(0.0, result, 0.001)
}
@Test
fun `回归验证:折扣不影响总额为负数的情况`() {
val items = listOf(Item(price = 100.0, quantity = 2, discount = 1.2))
val result = calculateTotal(items)
assertTrue(result < 0) // 以前支持负折扣?这里回归抓到了不一致
}
}
看到没?第二个测试就抓住了潜在问题——折扣超过100%时结果是负数,业务上可能不应该允许。回归测试帮你在小范围里发现这类“意想不到的涟漪”。
习惯建议:我习惯在每次功能开发前,先把涉及到的旧功能回归用例拉一遍。这样改完代码后只需要执行./gradlew test就能看到有没有“震到”老代码。
避坑指南
我曾经犯过三个典型错误,你注意避开:
- 只测新增,不测旧功能——这就是开篇那个线上故障的根源。记住:回归测试不是单元测试的附属,它是独立的质量门禁。
- 回归用例写得太“脆”——比如依赖具体时间戳、随机UUID、Mock固定返回值。这种用例经常误报,反而让人忽视真正的问题。
- 等到CI才跑回归——本地提交前跑一遍,能节省你半小时排队等CI结果的时间。
警告:不要滥用回归测试 并不是所有旧代码都需要回归。只对“核心公共模块”和“频繁变更区域”维护回归用例。否则测试维护成本会反噬项目。
现在你该做什么?
看完这章,我建议你打开一个现有的Kotlin项目:
- 找到至少一个曾经出过bug的核心函数,给它写一个简单的回归测试(用
@Test和assertEquals)。 - 然后故意改错一行代码,跑一下测试,感受“安”的存在。