Kotlin學習之旅(D10)- Unit Test with Kotlin

Kotlin學習之旅第十天

今天的主題是 - - Unit Test with Kotlin

前言

Kotlin學習之旅(D1)-學習計劃&基本語法

Kotlin學習之旅(D2)-基本語法

Kotlin學習之旅(D3)-類與繼承

Kotlin學習之旅(D4)-函數與Lambda表達式

Kotlin學習之旅(D5)-高級語法

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的地方

Snip20181018_3.png

Unit Test 例子

讓我們在 test 文件夾下創建一個名爲MyTest的文件:

class MyTest {

    @Test
    fun proof_of_concept() {
        assertEquals(4 , 2 + 2)
    }
}

這個時候看到左邊有個綠色的箭頭,點一下就能運行Unit Test

運行結果如下:

Snip20181018_3.png

可以看到我們寫的這個Unit Test通過了,這就是一個最簡單的例子,那麼代碼裏面寫的每一句是什麼意思呢?

  • @Test - 這個註解表示接下來的方法是一個Unit Test 方法,通過上面的import信息也能看出來,這個註解是屬於JUnit的
  • assertEquals(4, 2+2) - 這行代碼表示 判斷 4 是否等於 2+2,assertEquals方法同樣也是JUnit裏面的方法

所以這段代碼的執行結果很明顯是相等的,因此測試通過。

除了assertEquals,JUnit還提供了其他許多的判斷方法

Snip20181018_3.png

關於JUnit的一切詳情都可以從這裏找到:JUnit 5 User Guide

另外推薦一個學習JUnit的網站:極客學院JUnit測試框架

Unit Test in Android

經過簡單的介紹,我們已經知道了什麼是Unit Test,如果還不太清楚的話,可以看一下以下幾篇文章:

首先是關於TDD (Test-driven development)- 測試驅動開發

簡單介紹就是:先寫測試程序,然後編碼實現功能,使得測試程序能夠順利運行的一種開發方法。

  1. 測試驅動開發-維基百科
  2. 淺談測試驅動開發(TDD)
  3. Why Use Test-Driven Development?

然後是關於Unit Testing的:

  1. Unit testing - Wikipedia
  2. Unit Testing - Fundamentals

雖然在實際開發過程中,我們很少會使用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()
    }

運行結果如下:

Snip20181018_6.png

這個例子是不是特別簡單,它來自Mockito的官方,其實學習這些第三方框架,最好的教程就是他們的官方文檔,因此這裏我把幾個不錯的學習Mockito的資源列一下:

  1. Mockito 官網
  2. Mockito Github 地址
  3. Mocking-and-verifying
  4. Unit tests with Mockito - Tutorial

上面這個例子首先通過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)

這個例子的運行結果是:

Snip20181018_7.png

爲什麼會失敗呢? 因爲有這麼一行代碼

`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.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章