写点什么

Android 单元测试之 Mockk,idea 开发 android 教程

  • 2021 年 11 月 08 日
  • 本文字数:2792 字

    阅读完需:约 9 分钟

为什么不提供一个专门针对于 Kotlin 场景下的单元测试呢? 于是就有了 mockk


1.2 优势




  • 解决上述出现的问题

  • 支持 Kotlin,包括其语法糖


1.3 存在问题




目前官方文档给出了一些存在的问题:


  • PowerMock 如果需要和 Mockk 一起使用,则需要一个工作区 (Github issue

  • Inline 方法不支持 mock。


2. 使用


=======================================================================


2.1 导入




testImplementation "io.mockk:mockk:1.11.0"


2.2 一个例子




下面是官方文档的一个简单的示例,这是我们的被测试类:


class Car {


fun drive(direction: Direction): Outcome {


return Outcome.NO


}


}


enum class Direction {


NORTH,


SOUTH,


WEST,


EAST


}


enum class Outcome {


OK,


NO


}


这是我们的测试类:


class CarTest : TestCase() {


fun testDrive() {


// mock car 对象


val car = mockk<Car>()


// 设置监听


every { car.drive(Direction.NORTH) } returns Outcome.OK


// 执行


car.drive(Direction.NORTH)


// 验证


verify { car.drive(Direction.NORTH) }


// 双重验证


confirmVerified(car)


}


}


  • 可以看到 Mockk 使用了 Lambda 语句,这让代码实现变的很美观。

  • 其次 every{..} 语句用来设置监听,在 Mockito 中,它是 when ,其实作用是一样的,回调你想要的操作

  • 使用 verify{..} 进行验证,这是各个测试框架都通用的字段

  • 有个 confirmVerified 用来确认你的 mock 对象有没有被执行,因为前面已经有 verify 语句了,这里相当于一个二次确认,加不加都没什么关系


mockk 框架遵循 mock - 监听 - 执行 - 验证 的流程,所以如果你之前已经学习过 Mockito,那么你将更加容易上手 mockk。


2.3 DSL



2.3.1 mock 普通对象

通过语句 mockk<T>(...) 来 mock 一个对象,例如:


val car = mockk<Car>()


你可以使用 mockk() 来代替任何 mock 对象,比如说一个参数,下面我们要监听当执行调用某个方法时,返回一个 Car 实例:


every { ... } return Car()


如果我们不想这样做(因为可能会因为实例化太麻烦),可以这样写:


every { ... } return mockk()


我们可以在 mockk<T>() 构建时,填入一些参数,它的构造方法可填参数有这些:


inline fun <reified T : Any> 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<Engine>()


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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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

评论

发布
暂无评论
Android 单元测试之 Mockk,idea开发android教程