Android 单元测试之 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{...}coRuncoAssertcoAnswercoInvoke 等用于协程中的方法,后面就不再赘述了。

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())

传参说明如下:

  1. mock的类

  2. 要调用的方法名

  3. 方法传参,用array集起来

  4. 协程实例,没有的话可以用 mockk() 来替代

注:普通的mock类是可以可以这样使用的,但是对于 Object 类,需要在mock的时候设置 recordPrivateCalls 为true:

mockkObject(ObjectClass, recordPrivateCalls = true)

这样才能访问、调用object的私有方法

2.3.11 验证mock对象私有方法

验证是放在 verify{...} 中的,也是通过反射的方式来验证:

verify{ mockClass[“privateFunName”](arg1, arg2, …) }

主要分成三个部分:

  1. mock类

  2. 中括号,里面填入双引号+私有方法名

  3. 小括号,里面填入传参,可以使用 allAny<T>()mockk() … 或你想要的传入的实参

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

面试复习笔记:

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

[外链图片转存中…(img-ai4ytbCJ-1713735047657)]

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

[外链图片转存中…(img-XhrPxuvS-1713735047658)]

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 19
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值