本主題說明如何在評估每個片段行為的測試中納入架構提供的 API。
片段在應用程式中是可重複使用的容器,讓您能夠在各種活動和版面配置設定中呈現相同的使用者介面版面配置。鑒於片段的多功能性,請務必驗證片段是否提供資源利用率高的一致體驗。請注意以下事項:
- 片段不應依賴於特定的父活動或片段。
- 除非會向使用者顯示片段,否則不應建立片段的檢視區塊階層。
為了協助設定執行這些測試的條件,AndroidX fragment-testing 資料庫提供了 FragmentScenario 類別,用於建立片段並變更其 Lifecycle.State。
宣告依附元件
如要使用 FragmentScenario 將 fragment-testing-manifest 構件定義至
應用程式的 build.gradle 檔案使用 debugImplementation,以及使用 androidTestImplementation 的 fragment-testing 構件,如
範例:
Groovy
dependencies { def fragment_version = "1.8.9" debugImplementation "androidx.fragment:fragment-testing-manifest:$fragment_version" androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version" }
Kotlin
dependencies { val fragment_version = "1.8.9" debugImplementation("androidx.fragment:fragment-testing-manifest:$fragment_version") androidTestImplementation("androidx.fragment:fragment-testing:$fragment_version") }
本頁的測試範例會使用 Espresso 與 Truth 程式庫的斷言。如要瞭解其他可用的測試與斷言程式庫,請參閱「設定 AndroidX Test 的專案」。
建立片段
FragmentScenario 包含下面幾種用於在測試中啟動片段的方法:
launchInContainer(),用於測試片段的使用者介面。FragmentScenario會將片段附加到活動的根層級檢視區塊控制器。如果沒有,這個內含的活動就會是空的。launch(),用於在沒有片段使用者介面的情況下進行測試。FragmentScenario將這個類型的片段附加至一個「空白活動」,即沒有根層級檢視區塊的活動。
啟動其中一種片段類型後,FragmentScenario 會將受測的片段推動到指定狀態。根據預設,此狀態為 RESUMED,但您可以使用 initialState 引數覆寫此狀態。RESUMED 狀態表示片段正在執行,並且可供使用者查看。您可以使用 Espresso UI 測試來評估其 UI 元素的相關資訊。
以下程式碼範例示範了如何使用各種方法啟動片段:
launchInContainer() 範例
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
// The "fragmentArgs" argument is optional.
val fragmentArgs = bundleOf(“selectedListItem” to 0)
val scenario = launchFragmentInContainer<EventFragment>(fragmentArgs)
...
}
}
launch() 範例
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
// The "fragmentArgs" arguments are optional.
val fragmentArgs = bundleOf("numElements" to 0)
val scenario = launchFragment<EventFragment>(fragmentArgs)
...
}
}
提供依附元件
如果片段具有依附元件,則您可以向 launchInContainer() 或 launch() 方法提供自訂 FragmentFactory,藉此提供這些依附元件的測試版本。
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val someDependency = TestDependency()
launchFragmentInContainer {
EventFragment(someDependency)
}
...
}
}
如要進一步瞭解如何使用 FragmentFactory 為片段提供依附元件,請參閱「片段管理器」。
將片段推動到新狀態
在應用程式的 UI 測試中,通常只需要啟動受測的片段,並從 RESUMED 狀態開始對其進行測試即可。不過,在更精細的單元測試中,您可能也會評估片段從某個生命週期狀態轉換到另一個狀態時的行為。只要將 initialState 引數傳遞至任何 launchFragment*() 函式,即可指定初始狀態。
如要將片段推動至其他生命週期狀態,請呼叫 moveToState()。此方法支援以下狀態做為引數:CREATED、STARTED、RESUMED 與 DESTROYED。此方法會模擬如下情況,即片段或包含片段的活動出於任何原因而變更其狀態。
以下範例會啟動一個處於 INITIALIZED 狀態的測試片段,然後將其改為 RESUMED 狀態:
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val scenario = launchFragmentInContainer<EventFragment>(
initialState = Lifecycle.State.INITIALIZED
)
// EventFragment has gone through onAttach(), but not onCreate().
// Verify the initial state.
scenario.moveToState(Lifecycle.State.RESUMED)
// EventFragment moves to CREATED -> STARTED -> RESUMED.
...
}
}
重新建立片段
若應用程式在資源不足的裝置上執行,則系統可能會刪除包含片段的活動。這種情況下,應用程式需要在使用者返回時進行重新建立該片段。若要模擬這種情況,請呼叫 recreate():
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val scenario = launchFragmentInContainer<EventFragment>()
scenario.recreate()
...
}
}
FragmentScenario.recreate() 會刪除片段及其代管活動,然後重新加以建立。當 FragmentScenario 類別重新建立受測的片段時,該片段會返回刪除之前所處的生命週期狀態。
與 UI 片段互動
如要在受測的片段中觸發 UI 動作,請使用 Espresso 檢視區塊比對器與檢視區塊中的元素互動:
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val scenario = launchFragmentInContainer<EventFragment>()
onView(withId(R.id.refresh)).perform(click())
// Assert some expected behavior
...
}
}
若您需要對片段本身呼叫某種方法 (如回應選項選單中的選項),則您可以安全地執行此動作,方法為使用 FragmentScenario.onFragment() 並傳入 FragmentAction 來獲取對片段的參照:
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val scenario = launchFragmentInContainer<EventFragment>()
scenario.onFragment { fragment ->
fragment.myInstanceMethod()
}
}
}
測試對話方塊動作
FragmentScenario 亦支援測試對話方塊片段。雖然對話方塊片段具有 UI 元素,但其版面配置會在一個單獨的視窗中填入,而不是在活動本身。因此,請使用 FragmentScenario.launch() 測試對話方塊片段。
以下範例會測試對話方塊關閉流程:
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testDismissDialogFragment() {
// Assumes that "MyDialogFragment" extends the DialogFragment class.
with(launchFragment<MyDialogFragment>()) {
onFragment { fragment ->
assertThat(fragment.dialog).isNotNull()
assertThat(fragment.requireDialog().isShowing).isTrue()
fragment.dismiss()
fragment.parentFragmentManager.executePendingTransactions()
assertThat(fragment.dialog).isNull()
}
}
// Assumes that the dialog had a button
// containing the text "Cancel".
onView(withText("Cancel")).check(doesNotExist())
}
}