我們曾提到當你的程式與Android framework相依,在測試時就會是Instrumented tests,需要在實體裝置或模擬器執行。這會讓你的測試變慢,這篇將介紹怎麼透過Mock Android framework,讓你仍是使用Local unit tests。
延續上一篇註冊的案例。這次我們想要讓使用者在註冊成功時,把註冊的帳號儲存在App裡。在需要的時候,就可以直接使用。
而儲存資料,第一個會想到要用的就是SharePreference
了。
我們已經知道寫在Activity的不好測試,所以建立一個Repository
來處理儲存帳號至SharedPreference
的部分吧
class Repository(val context: Context) {
//儲存UserId至SharePreference
fun saveUserId(id: String) {
val sharedPreference = context.getSharedPreferences("USER_DATA", Context.MODE_PRIVATE)
sharedPreference.edit().putString("USER_ID", id).commit()
}
}
在註冊成功時呼叫儲存。
//註冊成功,儲存Id
Repository(this).saveUserId(loginId)
在saveUserId()看到儲存SharedPreference會用到Context
,就知道它應該會是一個Instrumented tests
。但我們希望可以用Local unit tests就可以在JVM上測試,因為這樣測試的速度較快。
在 Gradle 加上Mockito 框架,我們將使用Mockito
來模擬與SharedPreference的互動。還記得我們在介紹假物件Mock時,曾提到這樣的做法。
dependencies {
testImplementation 'org.mockito:mockito-core:2.21.0'
testImplementation 'org.mockito:mockito-inline:2.21.0’
}
測試步驟:
@Test
fun saveUserId() {
//步驟1.Mock Context、SharePreference
val sharedPrefs = mock(SharedPreferences::class.java)
val sharedPrefsEditor = mock(SharedPreferences.Editor::class.java)
val context = mock(Context::class.java)
//步驟2. 使用when thenReturn 讓Production code 呼叫sharedPreference時回傳模擬的物件
`when`(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs)
`when`(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
`when`(sharedPrefsEditor.putString(anyString(), anyString())).thenReturn(sharedPrefsEditor)
val userId = "A1234567"
val preKey = "USER_ID"
//步驟3. 執行被測試物件:Act 呼叫repository.saveUserId()
val repository = Repository(context)
repository.saveUserId(userId)
//步驟4. 使用verify method,驗證模擬物件是否有呼叫putString,並傳入正確的參數。
verify(sharedPrefsEditor).putString(
argThat { key -> key == preKey },
argThat { value -> value == userId }
)
//步驟5. 檢查SharedPreference是否有呼叫commit
verify(sharedPrefsEditor).commit()
}
點綠色三角型,執行測試。
可以看到測試通過,測試的時間是1秒6,這樣的Local tests比起直接用Instrument tests快多了。
直接Mock SharePreference不一定是好的解法。這邊給大家一個練習,另一個做法是增加一個中介層SharePreferenceManager,改去測試有沒有傳送正確的資料到ISharePreferenceManager,而不是去每次都要Mock SharePreference。
範例下載:
https://github.com/evanchen76/MockSharedPreferenceSample
下一篇我們就來看用搭配Instrument tests的話應該怎麼測試。
您好,
我想請問是不是要被測試的 function 一定要是 public 的才能夠被測試?
如果 saveUserId() 是一個 private function 那還有辦法測試它嗎?
應該是不能測試。不過其實private function是不需要測試的。因為private function應該至少被一個public function給呼叫到,而這個public function才是我們應該要測試的。
了解,但真的不會有狀況是只測很核心的 private function 嗎?(雖然這感覺比較像是 Unit test 而不是 Instrumented test 的範圍)
最近看到有些人會在 variable 或是 function 加上 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
,這會不會是更有彈性的作法呢?
https://developer.android.com/reference/androidx/annotation/VisibleForTesting
如果是現有的private function要加上測試,當然可以直接加囉。一般之所以不會測private還跟用TDD的方式開發有關。TDD從需求去產生測試案例,先寫測試案例再完成產品程式碼。所以我不太會是去測你說的核心,而是從"需求"去寫測試。
你可以參考我的這個分享。
https://www.youtube.com/watch?v=1h7dIbByhcs
Even 大大,太感謝了,我的 TDD 能力有待加強