Mockito使用方法(Kotlin)

2022-10-29 12:00:31

一、為什麼要使用Mockito

1.實際案例

1.1 遇到的問題

對於經常維護的專案,經常遇到一個實際問題:需求不停改變,導致架構經常需要修改某些概念的定義。

對於某些十分基礎又十分常用的概念,常常牽一髮而動全身。

此時,"重構-測試"迴圈將會消耗比較多的費用。

1.2 解決方法1

可以通過領域驅動開發,在設計架構之前和相關領域的專家充分溝通,從而從一開始就得到準確的定義。

同時,在開發過程中對於之後有可能增加新功能的模組,充分增加其可拓展性。

1.2 解決方法2

通過編寫高質量程式碼,保證單一功能由單一函數負責,從而減少增加新功能時的工作量。

1.3 根本原因

不論架構怎樣設計,對於一個經常維護、更新的專案,其必然會在某些時刻遇到修改基本定義的情況。

而這些基本定義的大量參照,對於一名普通開發者來說,不一定能完全照顧到。

即使照顧到了,也不能保證下一次也同樣不出問題。

1.4 解決方法3

使用自動化測試,在增加程式碼後用計算機代替人進行功能測試,從而大大提高測試效率。

在引入單元測試後,不僅能測試出「是否有問題」,更能高效找到錯誤出現的位置,效率顯著高於傳統的控制檯輸出偵錯方法。

2.行為驅動開發

2.1 概覽

BDD:行為驅動開發

第一步:編寫一個失敗的"客戶驗收測試",從客戶視角描述系統。

第二步:寫系統程式碼,直到驗收測試通過。

2.2 缺點

程式碼量大,前期成本高。

即該方法不適合一次性寫完,之後幾乎不維護的小型程式。

2.3 優點

後期開發效率大大提高。

因此,該方法適用於時常維護,使用時間很長的大型程式。

二、部署Mockito

1.gradle部署

build.gradle.kts

dependencies {
    testImplementation("org.mockito:mockito-core:4.3.1")
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2")
    testImplementation("org.mockito:mockito-inline:4.3.1")
    testImplementation(fileTree("testLibs"))
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2")
}

注意:testLibs可以改成任意資料夾名,這個資料夾放入你的外部依賴(如spigot的jar)

2.在哪裡寫測試程式碼

import org.junit.jupiter.api.Test;

public class TestStarter {

    @Test
    public void test(){
        TestCore.INSTANCE.test();
    }

}

3.ClassFormatError

這是因為你看起來匯入了API,實際上沒有匯入好。

解決方法:把API(如Spigot)的jar檔案放入testLibs(或你自己的資料夾名)

SpigotAPI下載地址:https://hub.spigotmc.org/nexus/content/repositories/snapshots/org/spigotmc/spigot-api/

4.ClassNotFoundError

這是因為你沒有匯入API。

解決方法:testImplementation("你需要的依賴"),範例如下

    testImplementation("io.izzel.taboolib:common:6.0.10-11")
    testImplementation("io.izzel.taboolib:module-configuration:6.0.10-11")

三、使用方法

1.mock

用來建立一個虛假的物件,如Player。

val player= Mockito.mock(Player::class.java)

這個物件的型別就是Player,可以傳遞給別的方法,並且可以自定義觸發player的方法時,執行和返回的內容。

2.when ... then ...

2.1 when ... thenReturn ...

val player=mock(Player::class.java)
`when`(player.name).thenReturn(playerName)
val playerLocation= mock(Location::class.java)
`when`(playerLocation.x).thenReturn(0.0)
`when`(playerLocation.y).thenReturn(0.0)
`when`(playerLocation.z).thenReturn(0.0)

這個方法可以用來指定返回值。

2.2 anyString()

對於有引數的方法,可以設定不論傳入什麼引數,都return特定值。

anyInt()等同理。

val map= Mockito.mock(ConfigFile::class.java)
Mockito.`when`(map.getString(anyString())).thenReturn("Test")

2.3.thenAnswer

有時候,我們設定了anyString()之後,想使用實際傳入的引數,就需要thenAnswer()。

arguments即為傳入的實際引數。

Mockito.`when`(map.getString(anyString())).thenAnswer { invocation ->
            val args = invocation.arguments
            var str = args[0] as String
            fakeMap.getString(str)
        }