// 验证
verify { car.drive(Direction.NORTH) }
// 双重验证
confirmVerified(car)
}
}
-
可以看到
Mockk
使用了 Lambda 语句,这让代码实现变的很美观。 -
其次
every{..}
语句用来设置监听,在Mockito
中,它是when
,其实作用是一样的,回调你想要的操作 -
使用
verify{..}
进行验证,这是各个测试框架都通用的字段 -
有个
confirmVerified
用来确认你的mock对象有没有被执行,因为前面已经有verify
语句了,这里相当于一个二次确认,加不加都没什么关系
mockk框架遵循 mock - 监听 - 执行 - 验证 的流程,所以如果你之前已经学习过 Mockito,那么你将更加容易上手mockk。
2.3.1 mock 普通对象
通过语句 mockk<T>(...)
来mock一个对象,例如:
val car = mockk()
你可以使用 mockk()
来代替任何mock对象,比如说一个参数,下面我们要监听当执行调用某个方法时,返回一个 Car 实例:
every { … } return Car()
如果我们不想这样做(因为可能会因为实例化太麻烦),可以这样写:
every { … } return mockk()
我们可以在 mockk<T>()
构建时,填入一些参数,它的构造方法可填参数有这些:
inline fun mockk(
name: String? = null,
relaxed: Boolean = false,
vararg moreInterfaces: KClass<*>,
relaxUnitFun: Boolean = false,
block: T.() -> Unit = {}
) {…}
简单列下它们的作用:
-
name
: mock对象的名称 -
relaxed
: 是否对其代码进行依赖,默认为否,这个参数比较关键,后续会更细的讲解一下 -
moreInterfaces
: 让这个mock出来的对象实现这些声明的接口 -
relaxUnitFun
:和relaxed
差不多,但是只针对于 返回值是Unit 的方法, 后续会讲解一下 -
block
: 该语句块表示你在创建完 mock 对象后的操作,相当于.also{ ... }
语句
2.3.2 relaxed 和 relaxUnitFun
在 mock 一个对象时,这两个参数的意义是什么呢? 举个例子,我现在有一个被测类 Car,它依赖于一个 Engine:
class Car(private val engine: Engine) {
fun getSpeed(): Int {
return engine.getSpeed()
}
}
class Engine {
fun getSpeed(): Int {
return calSpeed()
}
private fun calSpeed(): Int {
return 30
}
}
我们要测试 getSpeed()
,它依赖于 Engine 里的方法,所以我们需要 mockk 一下 Engine,那么写下下面的测试方法:
fun testCar() {
// mock engine对象
val engine = mockk()
val car = Car(engine)
// 这里是私有方法设置监听的写法:
every { engine"calSpeed" } returns 30
val speed = car.getSpeed()
assertEquals(speed, 30)
}
但是这里我们报了一个错误: io.mockk.MockKException: no answer found for: Engine(#1).getSpeed()
这是因为mockk是严格去执行每个方法,而 Engine虽然mock了出来,但是mockk并不知道 Engine.getSpeed()
需不需要往下执行,所以它抛出了一个错误。
这个时候,你有两种解决方案。
方案一:将 Engine 的构建从 mock 改成 spy,因为spy可以真实模拟对象行为: engine = spyk<Engine>()
方案二:抛弃 calSpeed 方法, 使用 every { engine.getSpeed() } returns 30
方案三:在 mock Engine 时, 将 relaxed
置为true, engine = mockk<Engine>(relaxed = true)
这就是 relaxed
的作用,它真实模拟mock对象的所以方法,产生依赖。
而 relaxedUnitFun
没有 relaxed
那么厉害去模拟所有方法,仅仅模拟 空返回的方法。
2.3.3 使用注解 mock
除了使用 mockk<T>()
mock,我们还可以使用 @Mock
注解来mock:
@MockK // 通过注解mock
lateinit var car1: Car
@RelaxedMockK // 通过注解mock,并设置relaxed
lateinit var car2: Car
@MockK(relaxUnitFun = true) // 通过注解mock,并设置relaxed
lateinit var car3: Car
然后需要在启动的时候去初始化一下这些注解:
@Before
fun setup() {
MockKAnnotations.init(this, relaxUnitFun = true)
}
2.3.4 every / coEvery
every{...}
语句 没有什么好解释的,它就是 Mockito 中的when
,用来监听指定的代码语句,并做出接下来的动作,例如:
-
return value
返回某个值 -
just Runs
继续执行(仅用于 Unit 方法) -
answer {}
执行某个语句块
因为Kotlin中有 协程 这个特性(本质上是线程),所以单元测试在执行时可能会遇到执行协程中代码的问题,这个时候如果需要监听,则需要使用 coEvery{ ... }
当然了除了 coEvery{...}
, 还有 coVerify{...}
、 coRun
、 coAssert
、 coAnswer
、coInvoke
等用于协程中的方法,后面就不再赘述了。
2.3.5 verify
verify
是用来检查方法是否触发,当然它也很强大,它有许多参数可选,来看看这些参数:
fun verify(
ordering: Ordering = Ordering.UNORDERED,
inverse: Boolean = false,
atLeast: Int = 1,
atMost: Int = Int.MAX_VALUE,
exactly: Int = -1,
timeout: Long = 0,
verifyBlock: MockKVerificationScope.() -> Unit
){}
他们作用如下:
-
ordering
: 表示verify{ .. }
中的内容(下面简称语句块)是按照顺序执行。 默认是无序的 -
inverse
:如果为true,表示语句块中的内容不发生(即方法不执行) -
atLeast
:语句块中方法最少执行次数 -
atMost
:语句块中方法最多执行次数 -
exactly
:语句块中的方法具体执行次数 -
timeout
:语句块内容执行时间,如果超过该事件,则测试会失败 -
verifyBlock
: Lambda表达式,语句块本身
除了这些,还有别的 verify 语句,方便你使用:
-
verifySequence{...}
:验证代码按顺序执行,而且要每一行的代码都要在语句块中指定出来。 -
verifyAll{...}
:验证代码全部都执行,没有顺序的规定 -
verifyOrder{...}
:验证代码按顺序执行
2.3.6 capture 和 slot
可以使用 slot
来抓取某一个值, 我们来在 Car 和 Engine 加一些方法:
class Car(private val engine: Engine) {
fun setSpeed(s: Int) {
engine.setSpeed(s)
}
}
class Engine {
fun setSpeed(speed: Int) {
}
}
接下来在 Car.speed
中抓取其传参:
fun testCar() {
val engine = mockk(relaxed = true)
val car = Car(engine)
// 使用 slot 来准备获取值
val mySlot = slot()
// 方法调用时, 抓取传参
every { engine.setSpeed(capture(mySlot)) } just Runs
car.setSpeed(8)
// 使用 slot.captured 来获取值
assertEquals(8, mySlot.captured)
}
2.3.7 mock 静态类
和 Mockito 差不多: mockkStatic(StaticClass::class)
2.3.8 mock Object类
Kotlin 中 Object 类使用较多,而使用 Mockito
时不好对其进行mock,而 mockk 自然是完全支持啦,它通过下面语句mock一个object类:
mockkObject(ObjectClass)
如果你要验证、执行 object类里面的私有方法,你需要在mock的时候指定一个值 recordPrivateCalls
, 它默认是false:
mockkObject(ObjectClass, recordPrivateCalls = true)
enum
类也是一样的mock方式
2.3.9 给mock对象设置私有属性
一般属性只分成共有和私有,对于共有来说比较简单,直接set就行了。
而私有属性的设置需要通过反射来实现,在 mockk 中,需要使用 InternalPlatformDsl
这个类:
InternalPlatformDsl.dynamicSetField(engine, “speed”, 30)
2.3.10 执行 mock对象私有方法
方法也是分成共有和私有,所以对于共有方法,直接调用就行了。
对于私有方法,也是通过反射来实现,也需要调用 InternalPlatformDsl
这个类:
InternalPlatformDsl.dynamicCall(engine, “calSpeed”, arrayOf(), mockk())
传参说明如下:
尾声
你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
“calSpeed”, arrayOf(), mockk())
传参说明如下:
尾声
你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-1OyQofCx-1714409686291)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!