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.