回归测试:为什么需要 MockK 和 JUnit5?

说起回归测试,老读者可能都懂——说白了就是验证新代码有没有把旧功能搞坏。

我刚开始写单元测试时也觉得这步挺烦的,直到有一次线上出了严重问题:一个小接口改了返回值类型,结果下游十几个消费方全部挂掉。从那以后,我再也不敢小看回归测试了。

本章我们先搞清楚三件事:回归测试到底是啥?MockK 凭什么能帮我们简化这过程?JUnit5 和之前的版本有啥不同?

回归测试:不只是重复跑一遍

很多人以为回归测试就是「把已有用例再跑一次」。其实没那么简单。

回归测试的核心目标:确保代码变更不会引入新的缺陷

一个典型的场景是:你改了一个工具类的内部逻辑,但它的公共接口没变。光看代码你可能觉得没问题,但实际运行时可能因线程安全、缓存失效等问题导致其他模块表现异常。

回归测试的关键点在于:

  • 覆盖全面:不只是测改动的代码,还要测所有依赖它的模块
  • 自动化:手工跑几百个用例不现实,必须能一键执行
  • 快速反馈:每次提交都能在几分钟内知道有没有破坏已有功能

我记得有个同事曾经靠手工跑回归测试,每次发版前熬两夜。后来引入自动化和 Mock 框架,半天就搞定了。这个效率差距,你想想看。

MockK:专为 Kotlin 打造的 Mock 框架

MockK 不是 Mockito 的 Kotlin 版本。它从设计上就考虑了 Kotlin 的特性,比如空安全、扩展函数、协程等。

我个人最喜欢它的几个特点:

  • 柯里化语法:用 every { } 定义行为,比 Mockito 的 when() 更自然
  • 对空安全的原生支持:不会因为 nullable 类型而报空指针
  • 协程 Mock:直接用 coEvery{ } 就能 Mock 挂起函数
  • 松耦合设计:不需要每次都指定 any() 参数
一个小经验:用 MockK 时尽量保持 Mock 对象在测试方法内创建。我以前尝试用全局 @Mock 注解,结果多个测试间相互干扰,排查起来特别痛苦。

举个最简单的例子:


// 不用 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 的注解。

避坑:我曾经在混合使用 JUnit4 和 JUnit5 依赖时踩过坑——字节码冲突导致部分测试不执行。建议项目中统一版本,要么全用 JUnit4,要么全用 JUnit5,别混着用。

MockK + JUnit5:天生一对?

我个人觉得,MockK 和 JUnit5 搭配起来确实很舒服。JUnit5 的 Extension 机制可以很容易地集成 MockK,比如用 @ExtendWith(MockKExtension::class) 来替代手动创建 Mock 对象。

不过千万别觉得有了框架就万事大吉。回归测试的根本在于设计——如果你的代码耦合太紧,Mock 再多也覆盖不了真实的业务逻辑。

回归测试的黄金法则:Mock 掉外部依赖,但别 Mock 掉业务逻辑

举个例子:你测试一个下单流程,应该 Mock 掉支付网关、短信服务这些外部系统,但商品价格计算、库存校验这些业务逻辑千万别 Mock,否则测了等于没测。

好了,第一张 SVG 图来了,我用它来展示这三者之间的关系:

回归测试:MockK + JUnit5 核心结构 回归测试核心结构图 回归测试 单元测试 集成测试 端到端测试 MockK Mocking JUnit5 Test Engine 测试报告 协同 回归测试通过 / 失败 反馈循环 说明: MockK 负责隔离外部依赖,JUnit5 提供测试执行框架。两者协同后形成回归测试的基础设施。 每次代码变更后,运行这些测试,结果决定回归是否通过。失败则意味着引入了缺陷。

上面这张图展示了我认为最重要的几个层级关系:回归测试需要覆盖单元、集成、端到端三个层面。MockK 主要负责单元和集成层面的隔离,JUnit5 则提供了运行这些测试的环境。下面还有反馈循环——每次测试失败都应该驱动代码改进,而不是简单跳过。

好了,第一章就到这里。你需要记住的核心就两点:

  • 回归测试不是例行公事,它是保护现有功能不被破坏的底线
  • MockK + JUnit5 是一对高效组合,但前提是你得理解它们各自的角色