Kotlin学习之旅第十天
今天的主题是 - - Unit Test with Kotlin
前言
Kotlin学习之旅(D6)-Kotlin Idioms part 1
Kotlin学习之旅(D7)-Kotlin Idioms part 2
Kotlin学习之旅(D8)-From Java to Kotlin
Kotlin学习之旅(D9)-Android Extensions
添加JUnit依赖
Kotlin里面的Unit Test 和 java里面大同小异,因此我们直接上手写一个测试就能掌握了。
说到Unit Test,就不得不提一下JUnit
了,这是一个第三方库,用途就是跑测试用例。因此天才第一步,我们需要在build.gradle里面添加依赖
dependencies {
...
testImplementation "junit:junit:4.12"
}
记得依赖方式是testImplementation,这样在打正式包的时候这个依赖被不会编译,可以减少APK的体积
实际上,在我们新建一个Android项目的时候,Android Studio会自动帮我们生成三个文件夹,分别是
- code
- test
- androidTest
test 和 androidTest 文件夹就是我们编写Unit Test的地方
Unit Test 例子
让我们在 test 文件夹下创建一个名为MyTest的文件:
class MyTest {
@Test
fun proof_of_concept() {
assertEquals(4 , 2 + 2)
}
}
这个时候看到左边有个绿色的箭头,点一下就能运行Unit Test
运行结果如下:
可以看到我们写的这个Unit Test通过了,这就是一个最简单的例子,那么代码里面写的每一句是什么意思呢?
- @Test - 这个注解表示接下来的方法是一个Unit Test 方法,通过上面的import信息也能看出来,这个注解是属于JUnit的
- assertEquals(4, 2+2) - 这行代码表示 判断 4 是否等于 2+2,assertEquals方法同样也是JUnit里面的方法
所以这段代码的执行结果很明显是相等的,因此测试通过。
除了assertEquals,JUnit还提供了其他许多的判断方法
关于JUnit的一切详情都可以从这里找到:JUnit 5 User Guide
另外推荐一个学习JUnit的网站:极客学院JUnit测试框架
Unit Test in Android
经过简单的介绍,我们已经知道了什么是Unit Test,如果还不太清楚的话,可以看一下以下几篇文章:
首先是关于TDD (Test-driven development)- 测试驱动开发
简单介绍就是:先写测试程序,然后编码实现功能,使得测试程序能够顺利运行的一种开发方法。
然后是关于Unit Testing的:
虽然在实际开发过程中,我们很少会使用TDD的方式进行项目开发,但是编写Unit Test还是有它的好处的,因此在Android开发里面,我们也需要掌握这项技能。 Let’s do it.
在Android里面进行测试,经常需要用到另外一个依赖库,叫做Mockito,那么下面我们就使用这个库来做个最简单的Unit Test
为什么要用Mockito
在JUnit中,我们可以验证有返回值方法是否正确,但是如果一个方法的返回值为void,也就是没有返回值,特别是这个方法是为了调用另外一个方法,那么这个时候就需要验证这个方法有没有被调用。我们就可以通过Mockito这个框架来验证。
怎么使用Mockito
在build.gradle文件中加入:
testImplementation "org.mockito:mockito-core:2.18.3"
在我们在MyTest文件中加入以下代码:
@Test
fun mockito_test() {
val mockedList = mock(mutableListOf<String>().javaClass)
//using mock object
mockedList.add("one")
mockedList.clear()
//verification
verify(mockedList).add("one")
verify(mockedList).clear()
}
运行结果如下:
这个例子是不是特别简单,它来自Mockito的官方,其实学习这些第三方框架,最好的教程就是他们的官方文档,因此这里我把几个不错的学习Mockito的资源列一下:
上面这个例子首先通过mock()创建List类,然后在mockedList中添加元素one,最后通过verify来验证mockedList有没有成功调用List里面的对应方法。我们一个个来看:
验证方法调用
通过verify
来验证方法是否被调用,例如:
mockedList.add("once")
mockedList.add("twice")
mockedList.add("twice")
mockedList.add("three times")
mockedList.add("three times")
mockedList.add("three times")
//following two verifications work exactly the same - times(1) is used by default
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once")
//exact number of invocations verification
verify(mockedList, times(2)).add("twice")
verify(mockedList, times(3)).add("three times")
//verification using never(). never() is an alias to times(0)
verify(mockedList, never()).add("never happened")
//verification using atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times")
verify(mockedList, atLeast(2)).add("three times")
verify(mockedList, atMost(5)).add("three times")
在verify
方法中,我们可以指定方法调用的次数,通过times(1)
的形式,大家有兴趣的话可以跑一下上面的代码,看一下测试能不能通过。
stubbing指定方法的实现
除了空方法,我们有时候还需要检验方法的返回值或者实现。这个时候就需要用到when
方法
看一个例子:
//You can mock concrete classes, not just interfaces
val mockedList = mock(LinkedList::class.java)
//stubbing
`when`(mockedList.get(0)).thenReturn("first")
`when`(mockedList.get(1)).thenThrow(RuntimeException())
//following prints "first"
System.out.println(mockedList.get(0))
//following throws runtime exception
System.out.println(mockedList.get(1))
//following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999))
//Although it is possible to verify a stubbed invocation, usually it's just redundant
//If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
//If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
verify(mockedList).get(0)
这个例子的运行结果是:
为什么会失败呢? 因为有这么一行代码
`when`(mockedList.get(1)).thenThrow(RuntimeException())
当mockedList.get(1)的时候,我们让他抛出异常,因此运行的结果就是我们写的RuntimeException。
所以when
方法是不是很有用呢,有点像if…else 和 switch…case 的体系,可以通过不同的参数来控制测试方法对应的结果。
除了方法的运行结果可以控制以外,我们还可以测试方法的参数
参数匹配
例子:
val mockedList = mock(LinkedList::class.java)
//stubbing using built-in anyInt() argument matcher
`when`(mockedList[anyInt()]).thenReturn("element")
//following prints "element"
System.out.println(mockedList[999])
//you can also verify using an argument matcher
verify(mockedList)[anyInt()]
这个例子的运行结果是pass,因为我们需要的参数是anyInt(),verify
的时候参数也是anyInt()。
注意:当你使用参数匹配的时候,所有的参数都必须是可匹配的,什么意思呢?看例子:
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
//above is correct - eq() is also an argument matcher
verify(mock).someMethod(anyInt(), anyString(), "third argument");
//above is incorrect - exception will be thrown because third argument is given without an argument matcher.
第一行代码是正确的,因为eq()也是一种匹配模式,但是第二行代码直接传入一个String就不是匹配模式,在这里会抛出异常。
最后,由于Mockito的功能十分强大,因此在这里我也只是简单介绍一些常见的用法,对Mockito有一个初步的认识,其他的在需要的时候再去官方文档学习就可以了。
Mockito官方文档:Mockito Docs
总结
除了这篇文章里面说到的JUnit,Mockito这两个框架,我们还有可能需要用到一个叫做Robolectric
的框架,这个框架是用来测试运行在JVM上的Android代码的,不过由于暂时我们还接触不到,所以这里就不讲了,在后面进行项目实战的时候如果有需要,我们再去学习~
到目前为止,使用Kotlin开发Android项目所需要的基本知识都已经学完了,那么接下来就是实战阶段!加油!
Day 10 - Learn Kotlin Trip, Completed.