互操作性

Compose 可与常见的测试框架集成。

与 Espresso 的互操作性

在混合应用中,您可以在视图层次结构中找到 Compose 组件,在 Compose 可组合函数中找到视图(通过 AndroidView 可组合函数)。

无需执行任何特殊步骤即可匹配这两种类型。您可以通过 Espresso 的 onView 匹配视图,并通过 ComposeTestRule 匹配 Compose 元素。

@Test
fun androidViewInteropTest() {
    // Check the initial state of a TextView that depends on a Compose state.
    Espresso.onView(withText("Hello Views")).check(matches(isDisplayed()))
    // Click on the Compose button that changes the state.
    composeTestRule.onNodeWithText("Click here").performClick()
    // Check the new value.
    Espresso.onView(withText("Hello Compose")).check(matches(isDisplayed()))
}

为 Compose 互操作测试添加了视图范围的语义

将 Compose 搜索范围限定为特定视图

将复杂的界面迁移到 Compose 时,您可能会遇到嵌套在多个传统 Android 视图(例如 RecyclerViewViewPager)内的相同 Compose 元素。在这些场景中,标准 Compose 搜索(例如 onNodeWithText("Save"))可能会失败并显示“找到了多个节点”错误。

您无需修改生产代码来注入动态测试标记以区分这些元素,而是可以直接将 Compose 测试限定为特定的 Android 视图。

在测试规则中使用 onRootWithViewInteraction API。此函数接受 Espresso ViewInteraction,让您能够利用 Espresso 来隔离特定的容器 View,并仅在该作用域限定的层次结构中执行 Compose 交互。

与列表项互动

如果您需要与特定 RecyclerView 行内的 Compose 元素互动,请使用 Espresso 找到该行,然后将 Compose 互动限定在该行内。这会忽略所有其他行中相同的 Compose 元素。

@Test
fun testComposeButtonInsideRecyclerViewItem() = runComposeUiTest {
    // Scroll to the desired position using Espresso
    Espresso.onView(withId(recyclerViewId))
        .perform(RecyclerViewActions.scrollToPosition<MyViewHolder>(3))

    // Define an Espresso ViewInteraction that uniquely identifies the row
    val rowView = Espresso.onView(
        allOf(
            withId(rootViewId),
            hasDescendant(withText("Item #3"))
        )
    )

    // Scope the Compose search strictly to that specific row View
    onRootWithViewInteraction(rowView)
        .onNode(hasText("Like"))
        .performClick()
}

解决 ViewPager 中的歧义

当内存中同时存在多个具有相同 Compose 布局的 fragment 时,您可以将搜索范围限定为特定 fragment 的根视图 ID,以防止匹配出现歧义。

@Test
fun testComposeButtonInsideViewPagerItem() = runComposeUiTest {
    // Swipe to the desired page using Espresso
    Espresso.onView(withId(viewPagerViewId)).perform(swipeLeft())

    // Identify the specific container view using Espresso
    val fragmentB = Espresso.onView(withId(fragmentRootViewId))

    // The generic text "Save" is now unique within this view scope
    onRootWithViewInteraction(fragmentB)
        .onNode(hasText("Save"))
        .assertIsDisplayed()
}

与 UiAutomator 的互操作性

默认情况下,只能通过便捷描述符(显示的文本、内容说明等)从 UiAutomator 访问可组合项。如果您想访问使用 Modifier.testTag 的任何可组合项,则需要为特定可组合项的子树启用语义属性 testTagsAsResourceId。对于没有任何其他唯一句柄的可组合项(例如 LazyColumn 等可滚动的可组合项),启用此行为非常有用。

只能在可组合项层次结构中的较高层级启用语义属性一次,以确保可以从 UiAutomator 访问具有 Modifier.testTag 的所有嵌套可组合项。

Scaffold(
    // Enables for all composables in the hierarchy.
    modifier = Modifier.semantics {
        testTagsAsResourceId = true
    }
){
    // Modifier.testTag is accessible from UiAutomator for composables nested here.
    LazyColumn(
        modifier = Modifier.testTag("myLazyColumn")
    ){
        // Content
    }
}

任何具有 Modifier.testTag(tag) 的可组合项都可通过使用 By.res(resourceName) 并利用相同的 tag 作为 resourceName 的方式访问。

val device = UiDevice.getInstance(getInstrumentation())

val lazyColumn: UiObject2 = device.findObject(By.res("myLazyColumn"))
// Some interaction with the lazyColumn.

其他资源

  • 在 Android 平台上测试应用:此 Android 测试主着陆页更全面地介绍了测试基础知识和技巧。
  • 测试基础知识:详细了解 Android 应用测试背后的核心概念。
  • 本地测试:您可以在自己的工作站上本地运行一些测试。
  • 插桩测试:最好也运行插桩测试。也就是说,直接在设备上运行的测试。
  • 持续集成:借助持续集成,您可以将测试集成到部署流水线中。
  • 测试不同的屏幕尺寸:由于用户可使用多种设备,因此您应测试不同的屏幕尺寸。
  • Espresso:虽然 Espresso 旨在用于基于 View 的界面,但其知识对于 Compose 测试的某些方面仍然很有帮助。