Interoperabilität

Compose lässt sich in gängige Test-Frameworks einbinden.

Interoperabilität mit Espresso

In einer Hybrid-App finden Sie Compose-Komponenten in Ansichtshierarchien und Ansichten in Compose-Composables (über das AndroidView-Composable).

Es sind keine besonderen Schritte erforderlich, um einen der beiden Typen abzugleichen. Sie gleichen Ansichten mit onView von Espresso und Compose-Elemente mit ComposeTestRule ab.

@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()))
}

Semantik mit Ansichtsbereich für Compose-Interoperabilitätstests hinzufügen

Compose-Suchen auf bestimmte Ansichten beschränken

Bei der Migration komplexer Benutzeroberflächen zu Compose kann es vorkommen, dass identische Compose-Elemente in mehreren herkömmlichen Android-Ansichten verschachtelt sind, z. B. in einer RecyclerView oder einer ViewPager. In diesen Szenarien kann eine standardmäßige Compose-Suche wie onNodeWithText("Save") mit dem Fehler „Mehrere Knoten gefunden“ fehlschlagen.

Anstatt den Produktionscode zu ändern, um dynamische Test-Tags einzufügen und diese Elemente zu unterscheiden, können Sie den Compose-Test direkt auf eine bestimmte Android-Ansicht beschränken.

Verwenden Sie die onRootWithViewInteraction API für Ihre Testregel. Diese Funktion akzeptiert einen Espresso-ViewInteraction, sodass Sie mit Espresso eine bestimmte Container-Ansicht isolieren und Compose-Interaktionen ausschließlich innerhalb dieser Hierarchie ausführen können.

Mit einem Listenelement interagieren

Wenn Sie mit einem Compose-Element in einer bestimmten RecyclerView-Zeile interagieren müssen, verwenden Sie Espresso, um die Zeile zu finden, und beschränken Sie dann die Compose-Interaktion auf diese Zeile. Identische Compose-Elemente in allen anderen Zeilen werden ignoriert.

@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()
}

Mehrdeutigkeit in ViewPager-Elementen auflösen

Wenn sich mehrere Fragmente mit identischen Compose-Layouts gleichzeitig im Speicher befinden, können Sie die Suche auf die Stammansichts-ID des jeweiligen Fragments beschränken, um Mehrdeutigkeiten zu vermeiden.

@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()
}

Interoperabilität mit UiAutomator

Standardmäßig sind Composables über UiAutomator nur über ihre praktischen Deskriptoren (angezeigter Text, Inhaltsbeschreibung usw.) zugänglich. Wenn Sie auf ein Composable zugreifen möchten, das Modifier.testTag verwendet, müssen Sie das semantische Attribut testTagsAsResourceId für den Unterbaum des jeweiligen Composables aktivieren. Das Aktivieren dieses Verhaltens ist nützlich für Composables, die kein anderes eindeutiges Handle haben, z. B. scrollbare Composables (z. B. LazyColumn).

Aktiviere die semantische Property nur einmal weit oben in der Hierarchie der zusammensetzbaren Funktionen, damit alle verschachtelten zusammensetzbaren Funktionen mit Modifier.testTag über UiAutomator zugänglich sind.

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
    }
}

Jedes Compose-Element mit dem Modifier.testTag(tag) kann mit By.res(resourceName) und demselben tag wie dem resourceName zugänglich gemacht werden.

val device = UiDevice.getInstance(getInstrumentation())

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

Zusätzliche Ressourcen

  • Apps auf Android testen: Auf der Haupt-Landingpage für Android-Tests finden Sie einen umfassenderen Überblick über die Grundlagen und Techniken des Testens.
  • Grundlagen des Testens: Hier finden Sie weitere Informationen zu den grundlegenden Konzepten für das Testen einer Android-App.
  • Lokale Tests: Einige Tests können lokal auf Ihrer Workstation ausgeführt werden.
  • Instrumentierte Tests: Es empfiehlt sich, auch instrumentierte Tests auszuführen. Das sind Tests, die direkt auf dem Gerät ausgeführt werden.
  • Continuous Integration: Mit Continuous Integration können Sie Ihre Tests in Ihre Bereitstellungspipeline einbinden.
  • Verschiedene Bildschirmgrößen testen: Da Nutzer so viele verschiedene Geräte zur Verfügung haben, sollten Sie verschiedene Bildschirmgrößen testen.
  • Espresso: Obwohl Espresso für ansichtsbasierte UIs gedacht ist, kann es auch für einige Aspekte von Compose-Tests hilfreich sein.