测试应用的 Activity

Activity 是应用中每次用户交互的容器,因此测试应用的 Activity 在设备级事件(例如以下事件)期间的行为方式非常重要:

  • 其他应用(例如设备的手机应用)中断了您的应用的 Activity。
  • 系统销毁并重新创建了您的 Activity。
  • 用户将您的 Activity 放在了新的窗口环境中,例如画中画 (PIP) 或多窗口。

具体而言,请务必确保您的 Activity 在响应 Activity 生命周期中描述的事件时行为正确。

本指南介绍了如何评估您的应用在 Activity 生命周期不同状态转换期间维护数据完整性和良好用户体验的能力。

驱动 Activity 状态

测试应用 Activity 的一个关键方面是,将应用的 Activity 置于特定状态。若要定义测试的此“给定”部分,请使用 ActivityScenario 实例(AndroidX Test 库的一部分)。使用此类,您可以将 Activity 置于模拟设备级事件的状态。

ActivityScenario 是一种跨平台 API,您可以在本地单元测试和设备上集成测试中使用它。在真实或虚拟设备上,ActivityScenario 提供线程安全,可同步测试的 Instrumentation 线程与运行受测 Activity 的线程之间的事件。

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

创建 Activity

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

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

创建 Activity 后,ActivityScenario 会将 Activity 转换到 RESUMED 状态。此状态表示您的 Activity 正在运行并对用户可见。在此状态下,您可以自由地使用 Espresso UI 测试与 Activity 的 View 元素进行交互。

Google 建议您在测试完成后对 Activity 调用 close。这会清理相关资源并提高测试的稳定性。ActivityScenario 实现了 Closeable 接口,因此您可以应用 use 扩展函数,或者在 Java 编程语言中使用 try-with-resources,以便 Activity 自动关闭。

或者,您可以使用 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() {
        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 字段的值,如下面的代码片段所示:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testResult() {
        launchActivity<MyActivity>().use {
            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 要求您在 Instrumentation 线程中运行它们。

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

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

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

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