UiAutomator 是可以在系统和已安装应用间进行测试的 UI 测试框架。它允许您与屏幕上的可见元素进行交互,无论哪个应用正处于屏幕焦点。
UiAutomator 不需要作为应用进程的一部分运行,它使您所编写的集成测试不必知道应用的内部情况。这对于生成 Baseline Profiles 和进行性能测试(使用 Jetpack Macrobenchmark)非常重要。为了提供可靠的测试结果,测试框架需要与应用交互,但却不能直接操纵应用。UiAutomator 便可以做到这一点,它以一般用户的方式提供输入事件,同时更加稳定。
UiAutomator 基本用法
UiAutomator 提供了多种访问方式访问屏幕上不同类型的 UI 元素。您可以通过 By
选择器类使用它们:
- 使用
By.text()
按文本标签访问元素; - 使用
By.desc()
按contentDescription
访问元素; - 按元素标志(如
By.scrollable()
、By.checkable()
、By.clickable()
等)访问元素; - 使用
By.res()
按其资源 IDandroid:id="@+id/some_id"
访问元素。
在 View 系统中,我们通常会设置 ID 以便访问并设置 View
的属性。在这种情况下,您可以使用 By.res(packageName,"some_id")
获取该 View 的引用,然后与其交互。
与 View 系统渲染 UI 的方式不同,Jetpack Compose 采用了声明的方式来描述 UI,因此不需要资源标识符(android:id
)。这对开发者们来说非常方便,但对于 UiAutomator 来说,访问没有唯一 ID(例如文本标签或元素标志)的元素可能会遇到问题。虽然从技术上讲,您可以使用 contentDescription
来访问一个 Compose 元素,但 contentDescription
参数是用于无障碍访问框架的,如果添加对使用者不重要的标记,会使依赖无障碍功能的用户难以使用您的应用。
不过,办法还是有的。您可以利用 Modifier.testTag()
访问 Jetpack Compose 元素。这一功能默认是关闭的,下面让我们看看如何启用它。
启用 UiAutomator 与 Jetpack Compose 的互操作
首先,您需要在测试的可组合项层次结构中启用 testTagAsResourceId
。此标志会将 testTag
转换为所有嵌套组合项的资源 ID。如果您的项目是单一 Activity Compose
项目,可以仅在可组合项树的根节点附近启用它。这会使 UiAutomator 可以访问其中嵌套的所有具有 Modifier.testTag
的可组合项。
注意:此标志在 Jetpack Compose 1.2.0 或更高版本中可用。如果您正在使用使用 compose-bom,则任何版本都将包含此功能。
在 Now in Android 示例中,我们修改了 Scaffold
,它是可组合项 NiaApp
的一部分。Scaffold
是根组合项(除了 NiaTheme
,它无法设置任何 Modifier
),您可以对其添加 Modifier.semantics
,并设置 testTagAsResourceId = true
,如下面的代码片段所示:
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
Scaffold(
modifier = Modifier.semantics {
testTagsAsResourceId = true
},
// ...
)
一旦你完成了上述操作,就可以在可组合项层次结构中的任何位置使用 Modifier.testTag("identifier")
。此时,“identifier” 将作为资源 ID 传递给 UiAutomator。
在 ForYouScreen 可组合项中,让我们将 Modifier.testTag("forYou:feed")
添加到 LazyVerticalGrid
中。传入的参数是任意的,你不需要使用与 Now in android 相同的参数。
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
LazyVerticalGrid(
modifier = modifier
.fillMaxSize()
.testTag("forYou:feed"),
// ...
)
现在您可以在 UI 测试中访问 LazyVerticalGrid
,而无需为了测试而牺牲 contentDescription
。在测试时,您可以使用 By.res("forYou:feed")
选择器。
在我们的用例中,我们使用 UiAutomator 进行基准测试并生成 Baseline Profiles。以下是生成 Baseline Profile 的一段示例代码:
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() {
rule.collectBaselineProfile(PACKAGE_NAME) {
// 这个代码块定义了应用的关键用户体验
// 此示例中我们的目标是优化应用的启动速度
pressHome()
startActivityAndWait()
}
}
}
这个测试会多次启动默认 Activity 来生成 Baseline Profile。
您还可以在启动优化的基础上更进一步,优化首屏加载 feed 的运行时性能。您可以使用 By.res("forYou:feed")
选择器等待并查找 feed 列表,并对其进行一些交互:
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() {
rule.collectBaselineProfile(PACKAGE_NAME) {
// 这一代码块定义了应用的关键用户体验
// 此示例中我们的目标是优化应用的启动速度
pressHome()
startActivityAndWait()
// 等待异步加载的内容
// 我们使用资源 ID "forYou:feed" 查找元素,该资源 ID 等同于 Modifier.testTag("forYou:feed")
device.wait(Until.hasObject(By.res("forYou:feed")), 5_000)
val feedList = device.findObject(By.res("forYou:feed"))
// 在两侧设置一些边距以避免触发系统导航
feedList.setGestureMargin(device.displayWidth / 5)
// 快速滑动 feed 列表
feedList.fling(Direction.DOWN)
device.waitForIdle()
feedList.fling(Direction.UP)
}
}
}
注意! By.res(packageName, "identifier")
和 By.res("identifier")
的使用场景有所不同。前者将在 UI 层次结构中搜索 @packageName:id/identifier
,而后者仅搜索元素 ID。后者的功能正是 Modifier.testTag
所需要的。
在我们的示例中,如您使用 By.res("com.google.samples.apps.nowinandroid", "forYou:feed")
,会导致 UiAutomator 尝试查找资源 ID 为 @com.google.samples.apps.nowinandroid:id/forYou:feed
的 UI 元素,但是这样的元素并不存在,所以会测试失败并抛出 java.lang.NullPointerException
。正确的方法是,不要包含包名,仅使用 By.res("forYou:feed")
,它会将资源 ID 解析为 forYou:feed
,并且可以正确地在屏幕上找到对应元素。
总结
阅读本文后,您应该已了解如何通过简单的操作,启用 Jetpack Compose 与 UiAutomator 间的互操作。
如果想要了解更完整的示例,请查看 Now in Android 项目中生成 Baseline Profiles 及测量性能的 benchmarks 模块。Github 仓库 performance-samples 中也利用了此互操作性,同时展示了其他性能示例。有关 Baseline Profiles 的更多信息,请查看文档,或者如果您还想更进一步,可以以查看我们的 Baseline Profiles codelab。此外,您还可以查看 UiAutomator 的最新更新。
谷饭原创编/译文章,作者:Tao, Yulu,转载请注明出处来自谷饭,并加入本文链接: https://www.goofan.com/2023/03/test-composables-with-uiautomator/。