Android 寫測試系列 (1) — MockK 介紹
以前介紹過 Android 的測試 Framework Kotest,接著想介紹如何利用 Kotest 撰寫不同類型的測試,其中包含一般物件、Singleton 物件、Coroutine 非同步以及 ViewModel,會由淺入深從 mock 物件開始到搭配一些情境來撰寫測試,撰寫方式大多是我自己摸索出來的,有更好的寫法也歡迎大家討論交流。
現在常見的 MVVM 架構可以拆分為 View — ViewModel — Model,ViewModel 為了完成來自 View 的 request,會需要跟 Model 拿需要的資料,而不管是透過網路或是從手機拿資料都有失敗的可能性,如果要針對商業邏輯做測試,我們就不會希望因為突發性事件而影響 ViewModel 的測試結果,所以我們可以 “模擬” 外部的物件,也稱為 mock
,這篇就會介紹如何使用 MockK 來 mock 物件,以及有哪些常用功能。
現在定義 Car
那我們來對 Car 寫些測試,要建立一個 mock 物件時我們可以這樣產生
其中的 T 代表該物件的 type
,然後我們用 Kotest 寫一條測試
附帶一提:從 Android Studio 執行 Kotest 的測試會需要安裝 Plugin
在執行完上面的測試後,會看到 Console 有這個錯誤
因為 Car 的物件是假的物件,當別人跟它拿資料時我們必須處理該給什麼資料,所以我們可以透過 every
和 coEvery
處理。coEvery 是用來處理 suspend function
,在 MockK 中看到 co 開頭的都是跟 Coroutine 有關,以後會常常看到。
然後我們就能把測試調整成這樣
在寫測試時有時候我們會希望該物件的部分邏輯真的能夠執行,如果我們 mock 了該物件,那些重要邏輯就得複製到測試中,這實在是太不切實際,所以對比於 mock 的全模擬,我們有另一個選擇是 spyk
,我們來舉個例子
我們可以把一個真實的物件丟給 spy 物件,當我們沒有指定 spy 物件該怎麼做時,就會照原本物件的設定動作;而有指定時就會照我們的設定而動作,因此會看到兩個 spy 物件執行 drive()
卻得到不同的結果。另外也看到用了 verify
來做驗證,verify 主要用途是確認某個動作的執行次數,常用情況有
exactly
為確切次數,atLeast
為最少次數,atMost
為最多次數
有 verify 就能幫助我們確認是不是有邏輯漏掉了,這是一定要會學起來的。另外也可以把 mock 物件丟給 spy 物件,也許某些較為困難的測試會變得簡單一些 (* ̄▽ ̄)/‧★*”`’*-.,_,.-*’`”*-.,_☆,.-*’`”*-.,_
在寫測試時我們會針對不同情況而有不同的測試資料,所以在設定 mock 物件時可以用多個 every 來達成這個需求
allAny()
代表該 type 的所有資料,用在 drive(location: String) 代表參數給任何字串都可以符合這條,所以參數除了 Taipei 和 America 外其他都會回傳 Unknown
。
當支援的 location 變多,就要再增加 every
的數量,可能就會增加撰寫測試上的麻煩,我們可以利用捕捉參數的功能來化簡成一個 every
就好
在之前的測試中我們都是用 returns 搭配 every,當邏輯變得複雜時,可以改用 answers { doSomething() }
來處理回傳的資料
前面有提到
因為 Car 的物件是假的物件,當別人跟它拿資料時我們必須處理該給什麼資料,所以我們可以透過 every 和 coEvery 處理。
但有些 function 是不用回傳值,如果我們每個 function 都要透過 every
設定動作(例:returns Unit),可能測試還沒寫完就先累死,MockK 提供了 2 種方式來處理這個問題
- mockk(relaxed = true)
這個會將未設定every
的 function 給個預設回傳值,例如null
, 空字串,Unit
…等,但我自己不大推薦使用這個,在產品不斷地發展之下,新舊邏輯衝突是很常見的,當不小心漏掉every
時,不同的邏輯在使用不同的參數 call 同樣的 function 拿到同樣的 null,讓你最後跑出來的測試結果可能會相同,等到新功能上線後才發現出事啦阿北就慘了,所以寧願在寫測試時多花一些時間寫every
也不要出事之後寫檢討報告 😢,還是減少使用吧! - mockk(relaxUnitFun = true)
這個跟上面的有點類似,但是只針對沒有回傳值(return Unit) 的 function 預設 return Unit,一般來說沒回傳值的 function 重點在於其實作內容,但 mock 物件直接讓你的實作內容變沒用,所以直接幫你省掉時間寫every { car.xxx() } returns Unit
就變得有用非常多,懶人就可以使用這個設定。
這次介紹的都是我覺得重要的基本功能,未來用到 MockK 其他功能時會一併介紹。下一篇會介紹 BDD(Behavior-driven development) 以及設定一個情境,將一段程式碼修改成容易寫測試的狀態並利用 DescribeSpec 撰寫測試。
最後幫大家整理出 3 個重點
相關連結: