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