测试您应用的 Fragment

Fragment 在应用中用作可重复使用的容器,使您可以在各种 Activity 和布局配置中呈现相同的界面布局。 鉴于这些 Fragment 的通用性,请务必验证它们是否能够提供一致的、具有资源效率的体验:

  • Fragment 的外观应在多个布局配置中保持一致,包括支持更大屏幕尺寸或横向设备屏幕方向的配置。
  • 除非 Fragment 对用户可见,否则请勿创建 Fragment 的视图层次结构。

本文档介绍如何在评估每个 Fragment 行为的测试中纳入由框架提供的 API。

推进 Fragment 状态

为帮助设置这些测试的执行条件,AndroidX 提供了一个库 FragmentScenario,以创建 Fragment 并更改其状态。

配置测试工件位置

为按预期用途使用 FragmentScenario,请在应用的测试 APK 中定义 Fragment 测试工件,如以下代码段所示:

app/build.gradle

dependencies {
    // ...
    debugImplementation 'androidx.fragment:fragment-testing:{{ fragment_version }}'
}

创建 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 正在运行。 如果正在测试的是图形片段,其也对用户可见,因此您可以使用 Espresso 界面测试 评估关于其界面元素的信息。

以下代码段展示了如何启动每种 Fragment:

图形 Fragment 示例

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "state" 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 "state" 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()。 此方法支持以下参数状态:CREATEDSTARTEDRESUMEDDESTROYED。 此操作模拟包含 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.
            }
        }
    }
}