回归测试:为什么需要 MockK 和 JUnit5?
说起回归测试,老读者可能都懂——说白了就是验证新代码有没有把旧功能搞坏。
我刚开始写单元测试时也觉得这步挺烦的,直到有一次线上出了严重问题:一个小接口改了返回值类型,结果下游十几个消费方全部挂掉。从那以后,我再也不敢小看回归测试了。
本章我们先搞清楚三件事:回归测试到底是啥?MockK 凭什么能帮我们简化这过程?JUnit5 和之前的版本有啥不同?
回归测试:不只是重复跑一遍
很多人以为回归测试就是「把已有用例再跑一次」。其实没那么简单。
一个典型的场景是:你改了一个工具类的内部逻辑,但它的公共接口没变。光看代码你可能觉得没问题,但实际运行时可能因线程安全、缓存失效等问题导致其他模块表现异常。
回归测试的关键点在于:
- 覆盖全面:不只是测改动的代码,还要测所有依赖它的模块
- 自动化:手工跑几百个用例不现实,必须能一键执行
- 快速反馈:每次提交都能在几分钟内知道有没有破坏已有功能
我记得有个同事曾经靠手工跑回归测试,每次发版前熬两夜。后来引入自动化和 Mock 框架,半天就搞定了。这个效率差距,你想想看。
MockK:专为 Kotlin 打造的 Mock 框架
MockK 不是 Mockito 的 Kotlin 版本。它从设计上就考虑了 Kotlin 的特性,比如空安全、扩展函数、协程等。
我个人最喜欢它的几个特点:
- 柯里化语法:用
every { }定义行为,比 Mockito 的when()更自然 - 对空安全的原生支持:不会因为 nullable 类型而报空指针
- 协程 Mock:直接用
coEvery{ }就能 Mock 挂起函数 - 松耦合设计:不需要每次都指定
any()参数
举个最简单的例子:
// 不用 MockK 的做法——手动创建假实现
val repository = object : UserRepository {
override fun findById(id: Long): User? {
return User(id, "test")
}
}
// 用 MockK——三行搞定
val repository = mockk<UserRepository>()
every { repository.findById(1L) } returns User(1L, "test")
后者不仅代码量少,而且边界条件更好控制。比如你想测用户不存在的情况,MockK 只需改一行 returns null。
JUnit5:现代化的测试框架
JUnit5 和 JUnit4 最大的区别,我总结下来就三点:
| 方面 | JUnit4 | JUnit5 |
|---|---|---|
| 架构 | 单体框架 | 模块化(Jupiter + Platform) |
| 扩展机制 | @RunWith + Rule | Extension + @RegisterExtension |
| 参数化测试 | 比较有限 | @CsvSource、@MethodSource 等丰富支持 |
| 嵌套测试 | 不支持 | @Nested 内部类 |
JUnit5 的核心其实就两个东西:
- JUnit Platform:在 JVM 上启动测试框架的基础
- JUnit Jupiter:提供新的编程模型和扩展 API
嗯,这里要注意一个事情:如果你用的是 JUnit4 的老习惯,把测试写成 @Test 但用的是 JUnit5 的依赖,运行是没问题的。但如果你想用它的新特性,比如 @DisplayName 或者 @Nested,就必须显式导入 Jupiter 的注解。
MockK + JUnit5:天生一对?
我个人觉得,MockK 和 JUnit5 搭配起来确实很舒服。JUnit5 的 Extension 机制可以很容易地集成 MockK,比如用 @ExtendWith(MockKExtension::class) 来替代手动创建 Mock 对象。
不过千万别觉得有了框架就万事大吉。回归测试的根本在于设计——如果你的代码耦合太紧,Mock 再多也覆盖不了真实的业务逻辑。
举个例子:你测试一个下单流程,应该 Mock 掉支付网关、短信服务这些外部系统,但商品价格计算、库存校验这些业务逻辑千万别 Mock,否则测了等于没测。
好了,第一张 SVG 图来了,我用它来展示这三者之间的关系:
上面这张图展示了我认为最重要的几个层级关系:回归测试需要覆盖单元、集成、端到端三个层面。MockK 主要负责单元和集成层面的隔离,JUnit5 则提供了运行这些测试的环境。下面还有反馈循环——每次测试失败都应该驱动代码改进,而不是简单跳过。
好了,第一章就到这里。你需要记住的核心就两点:
- 回归测试不是例行公事,它是保护现有功能不被破坏的底线
- MockK + JUnit5 是一对高效组合,但前提是你得理解它们各自的角色