Compose lässt sich in gängige Testframeworks einbinden.
Interoperabilität mit Espresso
In einer Hybrid-App finden Sie Compose-Komponenten in View-Hierarchien und Views in Compose-Composables (über das AndroidView-Composable).
Für beide Typen sind keine besonderen Schritte erforderlich. 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 auf Ansichtsebene für Compose-Interop-Tests hinzufügen
Compose-Suchanfragen auf bestimmte Ansichten beschränken
Beim Migrieren komplexer UIs zu Compose kann es vorkommen, dass identische Compose-Elemente in mehreren herkömmlichen Android-Ansichten verschachtelt sind, z. B. in einem RecyclerView oder einem ViewPager. In diesen Fällen schlägt eine standardmäßige Compose-Suche wie onNodeWithText("Save") möglicherweise mit dem Fehler „Multiple nodes found“ (Mehrere Knoten gefunden) fehl.
Anstatt den Produktionscode zu ändern, um dynamische Test-Tags einzuschleusen, mit denen diese Elemente unterschieden werden, können Sie den Compose-Test direkt auf eine bestimmte Android-Ansicht beschränken.
Verwenden Sie die onRootWithViewInteraction API in Ihrer Testregel. Diese Funktion akzeptiert ein Espresso-ViewInteraction. So können Sie Espresso verwenden, um eine bestimmte Container-Ansicht zu isolieren und Compose-Interaktionen ausschließlich innerhalb dieser Hierarchie auszuführen.
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 darauf.
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() }
Mehrdeutigkeiten in ViewPagern beheben
Wenn sich mehrere Fragmente mit identischen Compose-Layouts gleichzeitig im Arbeitsspeicher befinden, können Sie die Suche auf die Stamm-View-ID des jeweiligen Fragments beschränken, um Unklarheiten bei der Zuordnung 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 beliebiges Composable zugreifen möchten, das Modifier.testTag verwendet, müssen Sie die semantische Eigenschaft testTagsAsResourceId für den Unterbaum des jeweiligen Composables aktivieren. Dieses Verhalten ist nützlich für Composables, die keinen anderen eindeutigen Handle haben, z. B. scrollbare Composables wie LazyColumn.
Aktivieren Sie die semantische Property nur einmal hoch oben in der Hierarchie Ihrer Composables, damit alle verschachtelten Composables 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
}
}
Auf jedes Composable mit dem Modifier.testTag(tag) kann mit By.res(resourceName) zugegriffen werden, wobei derselbe tag wie der resourceName verwendet wird.
val device = UiDevice.getInstance(getInstrumentation())
val lazyColumn: UiObject2 = device.findObject(By.res("myLazyColumn"))
// Some interaction with the lazyColumn.
Zusätzliche Ressourcen
- Apps unter 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.