本次 more time 为大家带来 Mock 技术。Mock技术可以让你专注自己的逻辑,和同事用接口交流,从而自由飞翔,按时下班。
Mock就是模拟的意思。模拟一个类,而不用真实的类。这在单元测试里面的好处是解耦。即我只对这一个类的功能进行验证,和这个类相关的其他类,都被我Mock,他们的表现是由我决定的。
最简单的 Mock手段,是写一个类去继承真实的类,这固然可以,代码量不大并且易读。但是如果要 Mock的是一个复杂的接口,有一堆方法需要去实现,并且这个接口没有默认实现。那么就不能这样了。这时就需要 Mock 框架。
Spring Boot Test[1] 推荐 Java 用 Mock框架是 Mockito[2], Kotlin 用 MockK[3],但 Mockito 也是兼容 Kotlin的,只是 Mockk 提供了 Kotlin DSL 的语法更加优雅。接下来的一个小场景里我会使用 Kotlin + Mocktion + JUnit4。
Mockito 提供了 2种语法,一种是when…then…verify 的,一种是行为驱动开发BDD[4]的语法。given…will…then。老的语法和BDD语法并没有多大的不同,为了节能起见,只讲BDD的语法。
下面给一个场景,来讲述 Mock 技术。小王有个任务,写一个类,叫Y运算器。Y运算器的核心是y函数。y(a,b,c) = a + b * c。
但小王不会写乘法,因此小亦说我帮你写一个,你先等着吧。
小王是 moretime 的忠实读者,觉得被小亦 block 自己,这种事情不能忍,于是求小亦先写一个接口。
小亦便老老实实写了个接口。
interface Multiplier {
fun mul(a: Double, b: Double): Double
}
小王很开心,于是开始写自己代码。
小王先写个类的雏形。
class Y {
fun y(a: Double, b: Double, c: Double) = 0.0
}
小王是一个TDD爱好者,于是他开始写测试代码。
当然,严格的TDD是先写测试代码,然后测试失败,不能编译也算测试失败,然后再来修复。我觉得TDD刚提出来的时候肯定没什么好的IDE,不然直接下测试红一片谁能忍?所以先写个基本的模板,再写测试时可以的。
@Test
fun shouldReturn7whenCalcY123() {
val y = Y()
val ans = y.y(1.0, 2.0, 3.0)
assertEquals(7.0, ans)
}
显然不过
报了个错
junit.framework.AssertionFailedError:
Expected :7.0
Actual :0.0
OK,开始进入TDD的红绿循环了。
小王修改自己的代码,用上了小亦的接口 Multiplier
class Y(private val multiplier: Multiplier) {
fun y(a: Double, b: Double, c: Double) = a + multiplier.mul(b, c)
}
OK,写好了。肯定是对的。不过很尴尬,需要修改测试。这实际上也不是严格的TDD,不过无所谓了
但问题来了,小王这里目前并没有 Multiplier 的实现类,这也就意味着它没办法实例化一个Y。那就没办法测试了
好在小王是一个TDD爱好者,他使用的Mockito
Spring 里用 Mockito 直接 @Mock 注解来一波就好了
于是测试类改成这样
@Mock
lateinit var mockMultiplier: Multiplier
@Test
fun shouldReturn7whenCalcY123() {
val y = Y(mockMultiplier)
val ans = y.y(1.0, 2.0, 3.0)
assertEquals(7.0, ans)
}
这样Y就能实例化了,再次运行测试
Expected :7.0
Actual :1.0
结果是 1.0,说明这个 mockMultiplier.mul() 方法返回的是 0
这里便有1个知识点
Mock 对象的函数结果都是默认值
基本类型是0,对象就是 null
之后加一行
变成
@Test
fun shouldReturn7whenCalcY123() {
given(mockMultiplier.mul(2.0, 3.0)).willReturn(6.0)
val y = Y(mockMultiplier)
val ans = y.y(1.0, 2.0, 3.0)
assertEquals(7.0, ans)
}
再次运行便通过了测试
这就是 given.willReturn的BDD写法
小王通过了测试,便收工回家。小亦一个人加班写好了乘法器。第二天给了小王
class MultiplierImpl: Multiplier {
override fun mul(a: Double, b: Double) = a * b
}
小王心想小亦这不靠谱的以后万一把这个类给改错了,不行,我要加一个新的测试
于是写了个集成测试
@Test
fun shouldReturn7whenCalcY123UsingMultiplierImpl() {
val y = Y(MultiplierImpl())
val ans = y.y(1.0, 2.0, 3.0)
assertEquals(7.0, ans)
}
也通过了测试,说明没问题,可以开始下一个任务了
总结
@Mock可以Mock 出一个对象,类型是类或是接口都行- 用
given().willReturn()的定义Mock对象的行为
延伸阅读
- 关于单测函数名的规范,可以看《码出高效:Java开发手册》[5] 的第8章单元测试
- 关于 BDD,可以研究研究 cucumber [6],我有空也看看
- mockito 除了上文的 Mock 之外,还有
then,spy这两个函数 。then可以验证一个方法是否被调用,因为可能被间接调用;spy可以完全继承一个类,并重写新的方法 - Java测试驱动开发》[7] 也可以看看,这个有电子版
参考
[1] https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing
[2] https://site.mockito.org/
[3] https://mockk.io/
[4] https://en.wikipedia.org/wiki/Behavior-driven_development
[6] https://book.douban.com/subject/30333948/
[7] https://cucumber.io/
[8] https://book.douban.com/subject/27116715/
欢迎关注

本文介绍Mock技术在单元测试中的应用,通过示例展示如何使用Mockito框架模拟接口,实现解耦测试,提高开发效率。

996

被折叠的 条评论
为什么被折叠?



