互操作性

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 互操作性测试添加了 View 范围内的语义

将 Compose 搜索范围限定为特定 View

将复杂界面迁移到 Compose 时,您可能会遇到嵌套在多个传统 Android View(例如 RecyclerView 或 ViewPager)中的相同 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 测试的某些方面仍然很有帮助。