从 UiAutomator 中访问可组合项

本文将介绍如何启用 Jetpack Compose 应用与 UiAutomator 间的互操作。

从 UiAutomator 中访问可组合项

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() 按其资源 ID android: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/

(2)
Tao, YuluTao, Yulu谷饭作者
上一篇 2023年 3月 1日 下午2:56
下一篇 2023年 3月 8日 下午1:21

相关推荐

wechat
关注微信公众号