建構本機單元測試

「local」測試會直接在自有工作站上執行,而非在 Android 裝置或模擬器上執行。因此,SDK 使用本機 Java 虛擬機器 (JVM),而不是透過 Android 裝置執行測試。本機測試可讓您更快評估應用程式的邏輯。不過,系統無法與 Android 架構互動,會對可執行的測試類型設下限制。

「unit」測試可驗證一小段程式碼 (即「測試中的單位」) 的行為。方法是執行該程式碼並檢查結果。

單元測試通常很簡單,但如果「測試中的單元」並未考慮可測試性,其設定就會有問題:

  • 您必須可透過測試存取您要驗證的程式碼。例如,您無法直接測試私人方法。而是使用其公用 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.AssertHamcrestTruth。上方的程式碼片段是使用 junit.Assert 的範例。

可模擬的 Android 程式庫

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

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

模擬 Android 依附元件

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

您可以使用 Mockable Android 程式庫和 MockitoMockK 等模擬架構,在單元測試中編寫 Android 類別模擬的行為。

如要使用 Mockito 在本機單元測試中新增模擬物件,請遵循以下程式設計模型:

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

以下範例說明如何在使用 Mockito-Kotlin 建立的 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 Testing Codelab

錯誤:「方法 ... 未模擬」

如果您嘗試透過 Error: "Method ... not mocked 訊息存取其任何方法,可模擬的 Android 程式庫會擲回例外狀況。

如果擲回的例外狀況對測試有問題,您可以變更行為,讓方法根據傳回類型傳回空值或零。如要這麼做,請在 Groovy 中專案的頂層 build.gradle 檔案中加入下列設定:

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