建構本機單元測試

「本機」測試會直接在您自己的工作站上執行,而非在 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.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() 方法指定條件,並在符合條件時回傳值。

以下範例說明如何在 Kotlin 中使用 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 測試程式碼研究室

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

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

如果擲回的例外狀況對測試有問題,您可以變更行為,讓方法改為傳回空值或零,視傳回類型而定。方法是在 Groovy 中,在專案的頂層 build.gradle 檔案中新增以下設定:

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