Fragment 在应用中充当可重复使用的容器,使您能够在各种 Activity 和布局配置下呈现相同的界面布局。鉴于这些 Fragment 的通用性,请务必验证它们是否能够提供一致的、具有资源效率的体验:
- Fragment 在各种布局配置下的外观应一致,包括支持较大屏幕尺寸或横向设备方向的配置。
- 请勿为 Fragment 创建视图层次结构,除非该 Fragment 对用户可见。
本文档介绍如何在评估每个 Fragment 行为的测试中加入框架提供的 API。
推动 Fragment 的状态
为了帮助设置执行这些测试的条件,AndroidX 提供了一个 FragmentScenario
库,便于您创建 Fragment 并更改其状态。
声明依赖项
为了按预期使用 FragmentScenario
,请在应用的测试 APK 中定义 fragment-testing 工件,如以下代码段所示:
app/build.gradle
dependencies { def fragment_version = "1.2.4" // ... debugImplementation 'androidx.fragment:fragment-testing:$fragment_version' }
要查看此库的当前版本,请参阅版本页上有关 Fragment 的信息。
创建 Fragment
FragmentScenario
包括用于启动以下类型的 Fragment 的方法:
这些方法还支持以下类型的 Fragment:
- 包含界面的图形 Fragment。要启动此类 Fragment,请调用
launchFragmentInContainer()
。FragmentScenario
将 Fragment 附加到 Activity 的根视图控制器。此托管 Activity 原本为空。 - 非图形 Fragment(有时称为“无头 Fragment”),用于存储若干 Activity 中包含的信息或对这些信息进行短期处理。要启动此类 Fragment,请调用
launchFragment()
。FragmentScenario
将此类 Fragment 附加到一个完全为空的 Activity,即没有根视图的 Activity。
启动其中一种类型的 Fragment 后,FragmentScenario
将被测 Fragment 推动到 RESUMED
状态。此状态表示 Fragment 正在运行。如果您测试的是图形 Fragment,则它对用户也是可见的,因此您可以使用 Espresso 界面测试评估其界面元素的相关信息。
以下代码段演示了如何启动每种类型的 Fragment:
图形 Fragment 示例
@RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testEventFragment() { // The "fragmentArgs" and "factory" arguments are optional. val fragmentArgs = Bundle().apply { putInt("selectedListItem", 0) } val factory = MyFragmentFactory() val scenario = launchFragmentInContainer<MyFragment>( fragmentArgs, factory) onView(withId(R.id.text)).check(matches(withText("Hello World!"))) } }
非图形 Fragment 示例
@RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testEventFragment() { // The "fragmentArgs" and "factory" arguments are optional. val fragmentArgs = Bundle().apply { putInt("numElements", 0) } val factory = MyFragmentFactory() val scenario = launchFragment<MyFragment>(fragmentArgs, factory) } }
重新创建 Fragment
如果设备资源不足,系统可能会清除包含 Fragment 的 Activity,要求您的应用在用户返回到应用时重新创建该 Fragment。要模拟这种情况,请调用 recreate()
:
@RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testEventFragment() { val scenario = launchFragmentInContainer<MyFragment>() scenario.recreate() } }
当 FragmentScenario
类重新创建被测 Fragment 时,Fragment 将返回到重新创建前所处的生命周期状态。
将 Fragment 推动到新状态
在应用的界面测试中,通常只需启动并重新创建被测 Fragment 即可。但是,在更精细的单元测试中,您还可以评估 Fragment 从一个生命周期状态转换到另一个生命周期状态时的行为。
要将 Fragment 推动到其他生命周期状态,请调用 moveToState()
。此方法支持以下状态作为参数:CREATED
、STARTED
、RESUMED
和 DESTROYED
。此操作会模拟包含您的 Fragment 的 Activity 由于被其他应用或系统操作打断而更改其状态的情况。
以下代码段演示了 moveToState()
的示例用法:
@RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testEventFragment() { val scenario = launchFragmentInContainer<MyFragment>() scenario.moveToState(State.CREATED) } }
在 Fragment 中触发操作
要在被测 Fragment 中触发操作,请使用 Espresso 视图匹配器与视图中的元素互动:
@RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testEventFragment() { val scenario = launchFragmentInContainer<MyFragment>() onView(withId(R.id.refresh)) .perform(click()) } }
如果您需要对 Fragment 本身调用方法,例如响应选项菜单中的选择,您可以通过实现 FragmentAction
安全地执行此操作:
@RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testEventFragment() { val scenario = launchFragmentInContainer<MyFragment>() scenario.onFragment(fragment -> fragment.onOptionsItemSelected(clickedItem) { // Update fragment's state based on selected item. } } } }
测试对话框操作
FragmentScenario
还支持测试对话框。
即使对话框是图形 Fragment 的实例,您也可以使用 launchFragment()
方法,以便在对话框本身中填充该对话框的元素,而不是在启动对话框的 Activity 中填充。
以下代码段可测试对话框关闭过程:
@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.requireFragmentManager().executePendingTransactions() assertThat(fragment.dialog).isNull() } // Assumes that the dialog had a button // containing the text "Cancel". onView(withText("Cancel")).check(doesNotExist()) } } }