「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.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 在本機單元測試中新增模擬物件,請遵循以下程式設計模型:
- 按照「設定測試環境」一文的說明,在
build.gradle
檔案中加入 Mockito 程式庫依附元件。 - 在單元測試類別定義的開頭加入
@RunWith(MockitoJUnitRunner.class)
註解。此註解會告知 Mockito 測試執行器驗證您的架構使用情況是否正確,並簡化模擬物件的初始化作業。 - 如要為 Android 依附元件建立模擬物件,請在欄位宣告之前加上
@Mock
註解。 - 如要恢復依附元件的行為,您可以使用
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
}