iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Software Development

溫柔學姐的Kotlin補課/教學系列 第 29

MockK正式上場,似曾相識的場景

其實在程式考試結束後,連接著兩人關係的補課也該跟著結束了。

但是詩憶彷彿沒注意到這點似的,帶著早就準備好的甜食等在門口,看到最後一個考生離開馬上跑進教室。

「學姐!」

「嗯?」唯心本來正在擦拭白板,聽到聲音便偏過頭來看。

「學姐也累了吧?我這裡有餅乾和糖果唷。」

「系計中教室裡禁止飲食唷。」

「啊,不小心忘記了,那,等學姐出來再一起吃吧。」詩憶把食物收回包裡。

「好唷,我快收拾完了,對了,我聽說妳對MockK有興趣?」

「咦?學姐怎麼知道的?」

「當然是有人告訴我的呀。」唯心看了一下旁邊開著的電腦。「要不然我們先稍微研究一下再吃東西吧。」

打開MockK官網的網址和IDE,唯心注意到有版本搭配的說明。

From version 1.10.0 MockK does not support Kotlin 1.2.*,所以先檢查一下我們現在使用的kotlin版本吧。」於是乘載函式庫資訊的build.gradle.kts再次受到關注。

plugins {
    kotlin("jvm") version "1.5.10"
}

「是1.5.10。」詩憶說。

「對呀,那我們就可以安心使用1.10.0後的最新版本了。」唯心說著,就把函式庫連著變數直接黏貼上去。

dependencies {
//...
    testImplementation("io.mockk:mockk:{version}")
}

「學姐,稍等一下,我記得最新版本號在官網上面。」詩憶想要先離開IDE畫面。

「啊,不用那麼麻煩,來,我教妳用IDE拿最新版本的方法。」唯心點擊IDE下方的Dependencies。

圖1

打開了函式庫管理列表,點擊MockK的那列,選了版本表最上方的1.12.0。

圖2
圖3

build.gradle.kts裡的函式庫的版本號變數也自動替換上被選擇的版本。

dependencies {
//...
    testImplementation("io.mockk:mockk:1.12.0")
}

「好了,那我們來看看官網的範例吧,畢竟這麼多函式不可能每個都試一遍呀。」唯心笑笑。

圖4

val car = mockk<Car>()

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

car.drive(Direction.NORTH) // returns OK

verify { car.drive(Direction.NORTH) }

confirmVerified(car)

她看完範例後,很快就有了思路。「我覺得我們可以繼續用之前的飲料例子唷。」

class Drink(val name: String, val price: Int) {//飲料類別
    fun freeAdd(): Boolean {//是否免費加料
       return true 
    }
}

class DrinkOrder(private val drink: Drink) {//飲料訂單類別
    var price = drink.price
    fun add() {//加料
       if (!drink.freeAdd()) {
           price += 10
       }
    }
}

準備好飲料類別和飲料訂單類別後,唯心著手寫新測試。

import io.mockk.*
import kotlin.test.Test
import kotlin.test.assertEquals

class DrinkOrderTest {
    @Test
    fun add() {
        val drink = mockk<Drink>()
        every { drink.price } returns 60
        every { drink.freeAdd() } returns false
        val order = DrinkOrder(drink)
    }
}

「咦?所以我們不用真的寫一個Drink物件,本來還在想飲料訂單類別不需要飲料名字,還要想飲料名字有點麻煩的。」詩憶睜大眼睛。

「是呀,因為飲料訂單依賴著外部傳入的飲料物件,所以要用mockk<T>(...)的泛型概念建立飲料的假物件,不是本尊唷。接著用every假造必要的函式和變數的回傳,就可以在飲料訂單裡幫飲料加料了。針對外部物件的假造,我記得是用Stub來稱呼,但測試的主角是飲料訂單呢,所以接下來要驗證飲料訂單加料的結果。」

@Test
    fun add() {
        val drink = mockk<Drink>()
        every { drink.price } returns 60
        every { drink.freeAdd() } returns false
        val order = DrinkOrder(drink)

        order.add()
        verify{ drink.freeAdd() }
        assertEquals(60, order.price)
}

「為什麼要verify函式drink.freeAdd()呢?」

「喔,是為了確定前面的執行過程中有沒有呼叫到這個函式呀,因為order.add()的前提條件需要drink.freeAdd()。順帶一提,every的位置很重要唷,如果妳沒提供飲料資料就投進飲料訂單,會出現錯誤唷。」

class DrinkOrderTest {
    @Test
    fun add() {
        val drink = mockk<Drink>()
        val order = DrinkOrder(drink)
        every { drink.price } returns 60
        every { drink.freeAdd() } returns false
    }
}

出現錯誤no answer found for: Drink(#1).getPrice()。因為DrinkOrder初始化所要取得drink.price的值就是呼叫drink.getPrice()

class DrinkOrderTest {
    @Test
    fun add() {
        val drink = mockk<Drink>()
        every { drink.price } returns 60
        val order = DrinkOrder(drink)
        every { drink.freeAdd() } returns false
        order.add()
        verify{ drink.freeAdd() }
        assertEquals(60, order.price)
    }
}

「學姐,範例裡還有confirmVerified,妳沒加進去呢。」

「嗯⋯⋯其實那是用來確認是不是驗證過所有的函式,沒驗證的會出現在Not verified calls清單裡。順帶一提,只能驗證mock的對象,也就是說,我們只能confirmVerified(drink),不能confirmVerified(order),後者會出現錯誤can't find stub DrinkOrder@137ceb57。」

「學姐,既然範例都說完了,我們可以去吃點心了吧。」詩憶覺得她的大腦和腸胃正在一同發出抗議。

「喔,差點都忘記這回事了呢,我們走吧。」唯心轉身提起包,卻沒留意到旁邊有個被留在桌上的水杯。

杯裡的水,灑了出來。

急忙伸出手試圖阻止水往電腦濺的詩憶,突然腦海中有什麼閃過,感覺眼前的場景好像在以前也發生過。


上一篇
考試的日子
下一篇
終幕也是新的開始:請遵守軟體版本週期
系列文
溫柔學姐的Kotlin補課/教學31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言