Android TDD 系列 — 13 使用Robolectric 撰寫 Android test

Evan Chen
Evan Android Note
Published in
7 min readOct 1, 2019

到目前為止,我們在Android已經寫了這幾種測試。

Unit 測試:在JVM上執行的單元測試。
Instrumented 測試:與Android framework相依,需在模擬器或實機上執行測試。
UI 測試:一樣要在模擬器或實機上測試,UI測試更注重使用者的互動。

Instrumented 測試與UI測試都需要在模擬器或實機上執行,執行起來很花時間。這篇要介紹的是Robolectric,讓可以讓我們用單元測試的方式來執行Android Tests,也就是可以在JVM上執行測試,大大的提升了執行測試的效率。

延續上一個註冊的範例。這次我們要用Robolectric單元測試的方式來驗證跟View有關的:

  1. 當註冊失敗,則Alert「註冊失敗」
  2. 當註冊成功,則StartActivity 開啟註冊成功頁

Production code 如下

if (!isLoginIdOK) {
// 註冊失敗,資料填寫錯誤
val builder = AlertDialog.Builder(this)
builder.setMessage("帳號至少要6碼,第1碼為英文").setTitle("錯誤")
builder.show()
} else if (!isPwdOK) {
val builder = AlertDialog.Builder(this)
builder.setMessage("密碼至少要8碼,第1碼為英文,並包含1碼數字").setTitle("錯誤")
builder.show()
} else {
//註冊成功,儲存Id
Repository(this).saveUserId(loginId)
val intent = Intent(this, ResultActivity::class.java)
intent.putExtra("ID", loginId)
startActivity(intent)
}

我們將加上這幾個測試。

  1. 帳號輸入錯誤,需Alert
  2. 密碼輸入錯誤,需Alert
  3. 註冊成功,需StartActivity 至註冊成功頁

使用Robolectric

buide.gralde
加上testOptions.unitTests.includeAndroidResources = true

android{
testOptions.unitTests.includeAndroidResources = true
}

加上robolectric 元件

dependencies { 
testImplementation "org.robolectric:robolectric:4.3"
}

開始測試

新增MainActivityTest,因為是在JVM執行,測試程式要放在test的目錄,而不是androidTest

在測試程式的setup使用Robolectric來初始化MainActivity

private lateinit var activity: MainActivity@Before
fun setupActivity() {
MockitoAnnotations.initMocks(this)
activity = Robolectric.buildActivity(MainActivity::class.java).setup().get()
}

新增一個測試,驗證註冊成功是否有開啟ResultActivity

@Test
fun registerSuccessShouldDirectToResult() {

}

要知道有沒有使用startActivity開啟指定的Activity,需要建立一個ShadowActivity,將用來觀察是否有開啟別的Activity

@Test
fun registerSuccessShouldDirectToResult() {
val shadowActivity = Shadows.shadowOf(activity)
}

在loginId, password 輸入欄位,放入可以通過註冊驗證的值,確保待會執行測試會是走註冊成功的流程

@Test
fun registerSuccessShouldDirectToResult() {
//arrange
val shadowActivity = Shadows.shadowOf(activity)
val userId = "A123456789"
val userPassword = "a123456789"
activity.loginId.setText(userId)
activity.password.setText(userPassword)
}

開始驗證

點下註冊按鈕,驗證是否有開啟ResultActivity,要開啟一個Activity需要使用Intent。所以這裡要驗證的重點其實是Intent的資料是否正確。

1.Intent的目的地的Activity
2.Intent的size
3.Intent傳送的key與value

@Test
fun registerSuccessShouldDirectToResult() {
//點下註冊按鈕
activity.send.performClick()
//驗證註冊成功時,是否有開啟ResultActivity
val nextIntent = shadowActivity.nextStartedActivity
assertEquals(nextIntent.component!!.className, ResultActivity::class.java.name)
assertEquals(1, nextIntent.extras!!.size())
assertEquals(userId, nextIntent.extras!!.getString("ID"))
}

測試註冊失敗應Alert

要測試註冊失敗有沒有跳出Alert時,可以使用 ShadowAlertDialog.getLatestDialog() 來進行驗證。

@Test
fun registerFailShouldAlert() {

//arrange
val userId = "A1234"
val userPassword = "a123456789"
activity.loginId.setText(userId)
activity.password.setText(userPassword)
//點下註冊按鈕
activity.send.performClick()
val dialog = ShadowAlertDialog.getLatestDialog() //Assert
assertNotNull(dialog)
assertTrue(dialog.isShowing)
}

Robolectric讓我們像單元測試一樣的測試與Android UI元件的互動。

我們曾提過一個好的單元測試應具備**獨立(Independent)**的特性。這裡的測試存在一個問題,當send.setOnClickListener被觸發時,會呼叫RegisterVerify().isLoginIdVerify 檢查帳號是否正確,那如果這個功能壞掉了呢?那是不是在這個測試我想驗證的註冊成功是否startActivity就沒被驗證到了。

這裡給大家一個練習,讓大家可以先想看看怎麼解決。我們在介紹MVP、MVVM時,再來說明怎麼把View的處理再獨立出來。解決這個問題。

範例下載:
https://github.com/evanchen76/RobolectricSample

參考
http://robolectric.org/

小技巧
Shift + command + ; 列出最近執行的測試

Android TDD 系列
下一篇:14 使用Custom View Components提升可測試性

--

--