测试应用的 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() {
       launchActivity<MyActivity>().use {
       }
    }
}

创建 activity 后,ActivityScenario 会将 activity 转换为 RESUMED 状态。此状态表示您的 activity 正在运行且对用户可见。在此状态下,您可以使用 Espresso 界面测试随意与 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 会要求您在插桩线程中运行它们。

如需在被测 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()
            }
        }
    }
}