Interoperacyjność

Compose integruje się z popularnymi platformami testowymi.

Interoperacyjność z Espresso

W aplikacji hybrydowej komponenty Compose można znaleźć w hierarchii widoków, a widoki – w komponentach Compose (za pomocą funkcji kompozycyjnej AndroidView).

Nie musisz wykonywać żadnych specjalnych czynności, aby dopasować którykolwiek z tych typów. Widoki dopasowujesz za pomocą onView Espresso, a elementy Compose za pomocą ComposeTestRule.

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

Dodawanie semantyki o zakresie widoku na potrzeby testowania współdziałania Compose

Ograniczanie wyszukiwania w Compose do konkretnych widoków

Podczas przenoszenia złożonych interfejsów użytkownika do Compose możesz napotkać identyczne elementy Compose zagnieżdżone w wielu tradycyjnych widokach Androida, np. w RecyclerView lub ViewPager. W takich przypadkach standardowe wyszukiwanie w Compose, np. onNodeWithText("Save"), może zakończyć się błędem „Znaleziono wiele węzłów”.

Zamiast modyfikować kod produkcyjny, aby wstrzykiwać dynamiczne tagi testowe do odróżniania tych elementów, możesz ograniczyć zakres testu Compose bezpośrednio do konkretnego widoku Androida.

Użyj interfejsu API onRootWithViewInteraction w regule testowej. Ta funkcja akceptuje Espresso ViewInteraction, co pozwala wykorzystać Espresso do wyizolowania konkretnego widoku kontenera i wykonywania interakcji Compose wyłącznie w ramach tej hierarchii.

Interakcja z elementem listy

Jeśli chcesz wejść w interakcję z elementem Compose w konkretnym wierszu RecyclerView, użyj Espresso, aby znaleźć wiersz, a następnie ogranicz interakcję z Compose do tego wiersza. Spowoduje to zignorowanie identycznych elementów Compose we wszystkich pozostałych wierszach.

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

Rozwiązywanie niejednoznaczności w ViewPager

Gdy w pamięci znajduje się jednocześnie wiele fragmentów z identycznymi układami Compose, możesz ograniczyć wyszukiwanie do identyfikatora widoku głównego konkretnego fragmentu, aby uniknąć niejednoznaczności dopasowania.

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

Interoperacyjność z UiAutomator

Domyślnie elementy kompozycyjne są dostępne z poziomu UiAutomator tylko za pomocą wygodnych deskryptorów (wyświetlany tekst, opis treści itp.). Jeśli chcesz uzyskać dostęp do dowolnego elementu kompozycyjnego, który używa Modifier.testTag, musisz włączyć właściwość semantyczną testTagsAsResourceId dla poddrzewa danego elementu kompozycyjnego. Włączenie tego zachowania jest przydatne w przypadku funkcji kompozycyjnych, które nie mają żadnego innego unikalnego uchwytu, takich jak funkcje kompozycyjne z możliwością przewijania (np. LazyColumn).

Włącz właściwość semantyczną tylko raz, wysoko w hierarchii funkcji kompozycyjnych, aby mieć pewność, że wszystkie zagnieżdżone funkcje kompozycyjne z Modifier.testTag są dostępne z poziomu UiAutomator.

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

Każdy element kompozycyjny z Modifier.testTag(tag) może być dostępny za pomocą By.res(resourceName) przy użyciu tego samego tag co resourceName.

val device = UiDevice.getInstance(getInstrumentation())

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

Dodatkowe materiały

  • Testowanie aplikacji na Androida: główna strona docelowa testowania na Androidzie zawiera szerszy przegląd podstaw i technik testowania.
  • Podstawy testowania: dowiedz się więcej o podstawowych pojęciach związanych z testowaniem aplikacji na Androida.
  • Testy lokalne: niektóre testy możesz przeprowadzić lokalnie, na własnej stacji roboczej.
  • Testy z instrumentacją: warto też przeprowadzać testy z instrumentacją. czyli testy, które są uruchamiane bezpośrednio na urządzeniu.
  • Tryb ciągłej integracji: tryb ciągłej integracji umożliwia integrację testów z potokiem wdrażania.
  • Testowanie różnych rozmiarów ekranu: użytkownicy mają do dyspozycji wiele urządzeń, dlatego warto przetestować różne rozmiary ekranu.
  • Espresso: chociaż Espresso jest przeznaczone do interfejsów opartych na widokach, wiedza o nim może być przydatna w niektórych aspektach testowania Compose.