建構本機單元測試

「本機」測試會直接在工作站上執行,而非在 Android 裝置或模擬器上執行。因此,這類測試會使用本機 Java 虛擬機器 (JVM) 執行,而非 Android 裝置。本機測試可讓您更快速地評估應用程式邏輯。不過,由於無法與 Android 架構互動,因此可執行的測試類型會受到限制。

「單元」測試會驗證一小段程式碼 (即「受測單元」) 的行為。方法是執行程式碼並檢查結果。

單元測試通常很簡單,但如果受測單元在設計時未考量可測試性,設定可能會發生問題:

  • 您要驗證的程式碼必須可透過測試存取。舉例來說,您無法直接測試私人方法。而是使用其公用 API 測試類別。
  • 如要獨立執行單元測試,必須將受測單元的依附元件替換成您控制的元件,例如假元件或其他測試雙重元件。如果程式碼依附於 Android 架構,這個問題會特別嚴重。

如要瞭解 Android 常見的單元測試策略,請參閱「要測試的項目」。

本機測試位置

根據預設,本機單元測試的來源檔案會放在 module-name/src/test/ 中。使用 Android Studio 建立新專案時,這個目錄就已存在。

新增測試依附元件

您也需要為專案設定測試依附元件,才能使用 JUnit 測試架構提供的標準 API。

如要這麼做,請開啟應用程式模組的 build.gradle 檔案,並將下列程式庫指定為依附元件。使用 testImplementation 函式,表示這些依附元件適用於本機測試來源集,而非應用程式:

dependencies {
  // Required -- JUnit 4 framework
  testImplementation "junit:junit:$jUnitVersion"
  // Optional -- Robolectric environment
  testImplementation "androidx.test:core:$androidXTestVersion"
  // Optional -- Mockito framework
  testImplementation "org.mockito:mockito-core:$mockitoVersion"
  // Optional -- mockito-kotlin
  testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
  // Optional -- Mockk framework
  testImplementation "io.mockk:mockk:$mockkVersion"
}

建立本機單元測試類別

您會將本機單元測試類別編寫為 JUnit 4 測試類別。

如要這麼做,請建立包含一或多個測試方法的類別,通常位於 module-name/src/test/。測試方法會以 @Test 註解開頭,並包含程式碼,用於執行及驗證要測試的元件。

以下範例說明如何實作本機單元測試類別。測試方法 emailValidator_correctEmailSimple_returnsTrue() 會嘗試驗證 isValidEmail(),這是應用程式中的方法。如果 isValidEmail() 也傳回 true,測試函式就會傳回 true。

Kotlin

import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class EmailValidatorTest {
  @Test fun emailValidator_CorrectEmailSimple_ReturnsTrue() {
    assertTrue(EmailValidator.isValidEmail("name@email.com"))
  }

}

Java

import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

class EmailValidatorTest {
  @Test
  public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
    assertTrue(EmailValidator.isValidEmail("name@email.com"));
  }
}

您應建立可讀的測試,評估應用程式中的元件是否傳回預期結果。建議您使用 junit.Assert、Hamcrest 或 Truth 等斷言程式庫。上方程式碼片段是使用 junit.Assert 的範例。

可模擬的 Android 程式庫

執行本機單元測試時,Android Gradle 外掛程式會包含一個程式庫,當中包含 Android 架構的所有 API,且版本與專案中使用的版本相同。程式庫包含這些 API 的所有公開方法和類別,但方法中的程式碼已移除。如果存取任何方法,測試就會擲回例外狀況。

這樣一來,在參照 Android 架構中的類別 (例如 Context) 時,就能建構本機測試。更重要的是,您可以使用模擬架構搭配 Android 類別。

模擬 Android 依附元件

常見的問題是發現類別正在使用字串資源。您可以呼叫 Context 類別中的 getString() 方法,取得字串資源。不過,本機測試無法使用 Context 或其任何方法,因為這些方法屬於 Android 架構。理想情況下,對 getString() 的呼叫會移出類別,但這不一定可行。解決方法是建立 Context 的模擬或虛設常式,在叫用 getString() 方法時一律傳回相同的值。

透過 Mockable Android 程式庫和模擬架構 (例如 Mockito 或 MockK),您可以在單元測試中編寫 Android 類別模擬行為。

如要使用 Mockito 將模擬物件新增至本機單元測試,請按照下列程式設計模型操作:

  1. 按照「設定測試環境」一節所述,在 build.gradle 檔案中加入 Mockito 程式庫依附元件。
  2. 在單元測試類別定義的開頭,新增 @RunWith(MockitoJUnitRunner.class) 註解。這個註解會指示 Mockito 測試執行器驗證您是否正確使用架構,並簡化模擬物件的初始化作業。
  3. 如要為 Android 依附元件建立模擬物件,請在欄位宣告前加上 @Mock 註解。
  4. 如要虛設依附元件的行為,您可以使用 when() 和 thenReturn() 方法指定條件和符合條件時的傳回值。

以下範例說明如何建立單元測試,並使用 Mockito-Kotlin 建立的模擬 Context 物件。

import android.content.Context
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

private const val FAKE_STRING = "HELLO WORLD"

@RunWith(MockitoJUnitRunner::class)
class MockedContextTest {

  @Mock
  private lateinit var mockContext: Context

  @Test
  fun readStringFromContext_LocalizedString() {
    // Given a mocked Context injected into the object under test...
    val mockContext = mock<Context> {
        on { getString(R.string.name_label) } doReturn FAKE_STRING
    }

    val myObjectUnderTest = ClassUnderTest(mockContext)

    // ...when the string is returned from the object under test...
    val result: String = myObjectUnderTest.getName()

    // ...then the result should be the expected one.
    assertEquals(result, FAKE_STRING)
  }
}

如要進一步瞭解如何使用 Mockito 架構,請參閱 Mockito API 參考資料和程式碼範例中的 SharedPreferencesHelperTest 類別。也歡迎試試 Android 測試程式碼實驗室。

錯誤:「Method ... not mocked」

如果您嘗試使用 Error: "Method ... not mocked 訊息存取任何方法,Mockable Android 程式庫會擲回例外狀況。

如果擲回的例外狀況會對測試造成問題,您可以變更行為,讓方法改為傳回空值或零,具體取決於傳回類型。如要這麼做,請在專案的頂層 build.gradle 檔案中,以 Groovy 語言加入以下設定:

android {
  ...
  testOptions {
    unitTests.returnDefaultValues = true
  }