MockK 的兩三事

Chris
Gogolook Tech
Published in
6 min readJul 8, 2020

單元測試 (unit test) 已是許多初具規模的開發部門要求落實的一項重要開發流程,也是一項專案要進入正式發布階段的一個最低門檻。

如果你對單元測試已經非常熟悉,你可以跳過這篇文章直接去 MockK 官網學習如何使用 MockK。

跟 MockK 官網不同的地方是,這篇文章會以模擬真實案例的方式,讓你了解如何用 Kotlin + MockK 來撰寫基本的單元測試。

為什麼單元測試很重要,沒有會怎麼樣?

Photo by Lenin Estrada on Unsplash

狗狗魯克公司生產了一台家用機器人,號稱能夠包辦所有家事,唯一的缺點是預設音量過小,「這音量叫不醒我啊!」客戶不看操作手冊就對客服人員咆哮著,於是專案管理員找到了工程師克里斯,要求他調高預設音量,克里斯剛跟同事吵過架,非常不爽的就把預設的 50 分貝隨便加個 0,以預設 500 分貝的數值發布了新的韌體,這客戶開心的更新了韌體之後,就再也沒抱怨過了…

當然事實上揚聲器是不可能發出 500 分貝的聲音,但 — 沒經過單元測試的程式碼對於客戶來說就是如此可怕。

Photo by @chairulfajar_ on Unsplash

好可怕,那我們趕快寫單元測試吧

在寫單元測試之前,先介紹一個術語叫做 mock,顧名思義就是模擬/模仿的意思,通常我們寫測試時會 mock 一個變因,用來測試物件因為這個變因而實際造成的結果,例如現在我們抓到一隻狼人,想測試他看到滿月會不會變身,所以我們 mock 了一個月亮,再 mock 這月亮的狀態為滿月,將這顆月亮擺在狼人面前,再觀察這狼人,OK 變身了,passed!

扯遠了,我們說回出包的克里斯

這位工程師克里斯非常懊悔,決定為這台機器人的韌體寫單元測試,這台機器人 Robot 配置了一個 Power 用來充電,每次充電提供 50 毫安培的電量,簡易的程式碼如下:

實際使用 MockK 來寫測試

要測試 Robot 的各項功能,是否正確的使用 Power,所以我們 mock 一個 Power 出來,配置在新建立的 Robot 上面

MockK 較為嚴謹,會規定 mock 出來的物件,其所有 method 都需要自行定義,否則會報錯
另一種 mock 方式:Annotation

relaxed 設定為 true,即所有 method 都不需要自定義
relaxUnitFun 設定為 true,即所有回傳 unit 的 method 都不需要自定義,只需要定義有回傳值的 method

使用 every 來模擬行為

測試 Robot 的 charge()功能,是否正確使用 power.provide(),所以先針對 Power 能提供的電源做修改

我們模擬 provide 先後提供 10 毫安培和 20 毫安培的電量,所以驗證 robot 在先後兩次充電之後,是否如預期電量達到 30 毫安培

驗證流程

假設加入了 Power 的保護機制,為了維持 Power 零件壽命,Robot 每次charge都會先讓 Power prepare熱機,再執行 provide供電,最後執行 complete做安全斷電。

我們再加入一個 Battery 來取代原本過於簡化的 energy 跟 Power 配合,新的程式碼如下:

使用 verify 驗證函式是否確實執行

確保 Robot 在 charge 時,有執行到預期的 Power 功能,exactly 參數可以設定以檢驗確實的執行次數

使用 slot & capture 驗證傳入參數

如果我們想驗證每次 Robot charge 時,Power 提供給 Battery 的電量參數是否如預期

這邊用了一個 Int 形態的 slot 來讓 capture 填入捕捉到的數值,Power 預設的 provide 供電是 10,所以驗證最後的 slot.captured 應該要是 10

使用 mockkStatic 和 mockkObject

好,現在有個功能完整的 Robot 了(才怪),我想要委託一個 Simple Factory 來生產 Robot,但這個 free style 的工廠會隨機產出不同國家的 Robot,每個國家的 Robot 打招呼方式不同,該怎麼測試他真的產出我想要的 Robot 呢?

我們把 Robot 增加一個 function hello,再新增一個 TaiwanRobot 繼承 Robot,這樣一來我的 TaiwanRobot 就會用中文打招呼了,然後我再新增一個 FreeStyleRobotFactory,新的程式碼如下:

FreeStyleRobotFactory.createRobot() 使用了 RegionObjectUtils.getRandomRegion() 來亂數取得國家,再用對應的國家產生對應的機器人,因為亂數取得的國家是變因,但要 mock 它時會發現無法用 mockk<?> 的方式來模擬 RegionObjectUtils.getRandomRegion,因為他是一個 object,所以要用 mockkObjectmockkStatic的方式來 mock

使用 mockkConstructor 來模擬 constructor

假設現在連 TaiwanRobot 的打招呼方式都 free style 了,我該怎麼測試 createRobot 呢?

先實作到這裡,一個簡單的 Robot 單元測試就完成了(咦?一開始說好的預設音量的測試呢?)

最後

本篇僅介紹 MockK 的基礎用法,MockK 還有很多其他非常易於寫測試的功能,例如 every/verify搭配 and(), or(), less(), more(), etc. 的用法,或是例如 private functions mocking 的方法,測試 Kotlin Coroutines 的方法,等其他各種技巧可以參見官方網站的說明:https://mockk.io/

Enjoy!

Photo by Azharul Islam on Unsplash

參考連結

https://mockk.io/
https://medium.com/joe-tsai/mockk-%E4%B8%80%E6%AC%BE%E5%BC%B7%E5%A4%A7%E7%9A%84-kotlin-mocking-library-part-1-4-39a85e42b8

--

--