测试应用的 activity

Activity 是应用中每次用户互动的容器,因此测试您应用的 Activity 在发生以下设备级事件时的行为表现非常重要:

  • 其他应用(如设备的电话应用)中断了您应用的 Activity。
  • 系统销毁又重新创建了您的 Activity。
  • 用户将您的 Activity 放置在新的窗口环境中,例如画中画 (PIP) 或采用多窗口模式的环境。

特别是要确保您的 Activity 在响应 Activity 生命周期中所述的事件时表现出正确的行为。

本指南介绍如何评估当应用的 Activity 在其生命周期的不同状态之间转换时,应用是否能够维持数据的完整性和良好的用户体验。

在 Compose 中测试 Activity

在测试使用 Jetpack Compose 构建的应用时,您通常使用 createAndroidComposeRule 启动 Activity 并与您的 界面组件互动。

不过,测试设备级事件(例如配置更改或 Activity 被系统置于后台或销毁)需要您直接操控 Activity 的生命周期。为此,您需要使用 底层 ActivityScenario 框架。

Compose 测试规则会自动为您封装和管理此场景。 在本指南中,您将看到以下模式用于弥合现代界面测试与标准生命周期管理之间的差距:

@get:Rule
val composeTestRule = createAndroidComposeRule<MyActivity>()

@Test fun testEvent() {
    val scenario = composeTestRule.activityRule.scenario

    // ...
}

推动 Activity 的状态转换

测试应用 Activity 的一个关键环节就是将应用的 Activity 置于特定的状态。要定义这个 "指定" 的测试环节,请使用 ActivityScenario 实例,这是 AndroidX Test 库的一部分。通过使用此类,您可以将 Activity 置于模拟设备级事件的状态。

ActivityScenario 是一种跨平台 API,可用于本地单元测试和设备端集成测试等。在真实或虚拟设备上,ActivityScenario 可提供线程安全,在测试的插桩线程和运行被测 Activity 的线程之间同步事件。

该 API 特别适合用来评估被测 Activity 在被销毁或创建时的行为。本部分介绍与此 API 相关的最常见用例。

创建 Activity

要创建被测 Activity,请添加以下代码段中所示的代码:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
       launchActivity<MyActivity>().use {
       }
    }
}

创建 Activity 后,ActivityScenario 会将 Activity 转换为 RESUMED 状态。此状态表示您的 Activity 正在运行并且对用户可见。在此状态下,您可以使用 Compose 测试 API 随意地和 Activity 的 可组合项进行互动。

Google 建议您在测试完成后对 Activity 调用 close。 这会清理关联的资源并提高测试的稳定性。ActivityScenario 实现 Closeable,因此您可以应用 use 扩展程序,以便 Activity 自动关闭。

或者,您也可以使用 createAndroidComposeRule 在每次测试之前自动 启动 Activity、处理清理工作,并授予您对 Compose 界面测试方法和底层 ActivityScenario 的访问权限。以下示例展示了如何定义规则并从中获取场景实例:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Test fun testEvent() {
        val scenario = composeTestRule.activityRule.scenario
    }
}

使 Activity 转换到新的状态

要使 Activity 转换到其他状态(例如 CREATEDSTARTED),请调用 moveToState。此操作会分别模拟您的 Activity 因被其他应用或系统操作打断而停止或暂停的情况。

以下代码段演示了 moveToState 的示例用法:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.moveToState(State.CREATED)
        }
    }
}

确定当前的 Activity 状态

要确定被测 Activity 的当前状态,请获取 ActivityScenario 对象中 state 字段的值。如果被测 Activity 重定向到其他 Activity 或自行完成,则检查该 Activity 的状态特别有用,如以下代码段所示:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.onActivity { activity ->
              startActivity(Intent(activity, MyOtherActivity::class.java))
            }

            val originalActivityState = scenario.state
        }
    }
}

重新创建 Activity

如果设备资源不足,系统可能会销毁 Activity,并要求应用在用户返回时重新创建该 Activity。要模拟这些情况,请调用 recreate

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.recreate()
        }
    }
}

ActivityScenario 类会保留 Activity 的已保存实例状态以及使用 @NonConfigurationInstance 注释的所有对象。这些对象会加载到被测 Activity 的新实例中。

检索 Activity 结果

要获取与已完成的 Activity 相关联的结果代码或数据,请获取 ActivityScenario 对象中的 result 字段的值。使用 createAndroidComposeRule,您可以轻松触发完成 Activity 的界面操作,如以下代码段所示:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Test fun testResult() {
        composeTestRule.onNodeWithTag("finish_button").performClick()

        val scenario = composeTestRule.activityRule.scenario
        val resultCode = scenario.result.resultCode
        val resultData = scenario.result.resultData
    }
}

触发 Activity 中的操作

ActivityScenario 内的所有方法都是阻塞调用,因此该 API 会要求您在插桩线程中运行它们。

要触发被测 Activity 中的操作,请使用 Compose 测试 API 与可组合项互动:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Test fun testEvent() {
        composeTestRule.onNodeWithText("Refresh").performClick()
    }
}

不过,如果您需要对 Activity 本身调用方法,您可以通过使用 onActivity 安全地执行此操作:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.onActivity { activity ->
              activity.handleSwipeToRefresh()
            }
        }
    }
}

其他资源

如需详细了解测试,请参阅以下其他资源:

文档