测试应用的 Activity

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

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

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

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

推动 Activity 的状态转换

测试应用 Activity 的一个关键环节就是将应用的 Activity 置于特定的状态。要定义这个“指定”的测试环节,请使用 AndroidX Test 库中的 ActivityScenario 实例。通过使用这个类,您可以将 Activity 置于模拟本页开头所述的设备级事件的状态。

ActivityScenario 是一种跨平台 API,可用于本地单元测试和设备端集成测试等。在真实或虚拟设备上,ActivityScenario 可提供线程安全,在测试的插桩线程和运行被测 Activity 的线程之间同步事件。该 API 还特别适合用来评估被测 Activity 在被销毁或创建时的行为。

本部分介绍与此 API 相关的最常见用例。

创建 Activity

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

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @Test fun testEvent() {
            val scenario = launchActivity<MyActivity>()
        }
    }
    

创建 Activity 后,ActivityScenario 会将 Activity 转换为 RESUMED 状态。此状态表示您的 Activity 正在运行并且对用户可见。在此状态下,您可以使用 Espresso 界面测试随意地和 Activity 的 View 元素进行互动。

或者,您也可以使用 ActivityScenarioRule 在每次测试之前自动调用 ActivityScenario.launch,在测试结束后自动调用 ActivityScenario.close。以下示例展示了如何定义规则并从中获取场景实例:

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @get:Rule var activityScenarioRule = activityScenarioRule<MyActivity>()

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

使 Activity 转换到新的状态

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

以下代码段展示了 moveToState() 的用法示例:

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

确定当前的 Activity 状态

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

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @Test fun testEvent() {
            val scenario = launchActivity<MyActivity>()
            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() {
            val scenario = launchActivity<MyActivity>()
            scenario.recreate()
        }
    }
    

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

检索 Activity 结果

要获取与已完成的 Activity 相关联的结果代码或数据,请获取 ActivityScenario 对象中的 result 字段的值,如以下代码段所示:

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @Test fun testResult() {
            val scenario = launchActivity<MyActivity>()
            onView(withId(R.id.finish_button)).perform(click())

            // Activity under test is now finished.

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

触发 Activity 中的操作

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

要触发被测 Activity 中的操作,请使用 Espresso 视图匹配器与视图中的元素互动:

    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
        @Test fun testEvent() {
            val scenario = launchActivity<MyActivity>()
            onView(withId(R.id.refresh)).perform(click())
        }
    }
    

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

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

要详细了解线程在 Android 测试中的运行方式,请参阅了解测试中的线程