Typowe wzorce

Aplikację napisaną w Compose możesz testować za pomocą sprawdzonych metod i wzorców.

Testowanie w izolacji

ComposeTestRule umożliwia rozpoczęcie działania wyświetlającego dowolny element kompozycyjny: całą aplikację, pojedynczy ekran lub mały element. Warto też sprawdzić, czy funkcje kompozycyjne są prawidłowo hermetyzowane i działają niezależnie, co ułatwia i usprawnia testowanie interfejsu.

Nie oznacza to, że należy tworzyć tylko jednostkowe testy interfejsu. Bardzo ważne jest też określanie zakresu testów interfejsu, które obejmują większe części interfejsu.

Dostęp do aktywności i zasobów po ustawieniu własnych treści

Często musisz ustawić testowaną treść za pomocą composeTestRule.setContent, a także uzyskać dostęp do zasobów Activity, np. aby sprawdzić, czy wyświetlany tekst pasuje do zasobu tekstowego. Nie możesz jednak wywołać funkcji setContent w przypadku reguły utworzonej za pomocą funkcji createAndroidComposeRule(), jeśli aktywność już ją wywołuje.

Często stosowanym wzorcem na osiągnięcie tego celu jest utworzenie AndroidComposeTestRule za pomocą pustej aktywności, takiej jak ComponentActivity.

class MyComposeTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @Test
    fun myTest() {
        // Start the app
        composeTestRule.setContent {
            MyAppTheme {
                MainScreen(uiState = exampleUiState, /*...*/)
            }
        }
        val continueLabel = composeTestRule.activity.getString(R.string.next)
        composeTestRule.onNodeWithText(continueLabel).performClick()
    }
}

Pamiętaj, że ComponentActivity musi zostać dodany do pliku AndroidManifest.xml aplikacji. Aby to zrobić, dodaj tę zależność do modułu:

debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")

Niestandardowe właściwości semantyczne

Możesz tworzyć niestandardowe właściwości semantyczne, aby udostępniać informacje testom. Aby to zrobić, zdefiniuj nowy SemanticsPropertyKey i udostępnij go za pomocą SemanticsPropertyReceiver.

// Creates a semantics property of type Long.
val PickedDateKey = SemanticsPropertyKey<Long>("PickedDate")
var SemanticsPropertyReceiver.pickedDate by PickedDateKey

Teraz użyj tej właściwości w modyfikatorze semantics:

val datePickerValue by remember { mutableStateOf(0L) }
MyCustomDatePicker(
    modifier = Modifier.semantics { pickedDate = datePickerValue }
)

W testach użyj SemanticsMatcher.expectValue, aby potwierdzić wartość właściwości:

composeTestRule
    .onNode(SemanticsMatcher.expectValue(PickedDateKey, 1445378400)) // 2015-10-21
    .assertExists()

Weryfikowanie przywracania stanu

Sprawdź, czy stan elementów Compose jest prawidłowo przywracany po ponownym utworzeniu aktywności lub procesu. Przeprowadzaj takie kontrole bez polegania na ponownym tworzeniu aktywności za pomocą klasy StateRestorationTester.

Ta klasa umożliwia symulowanie ponownego tworzenia komponentu. Jest to szczególnie przydatne do weryfikowania implementacji rememberSaveable.


class MyStateRestorationTests {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun onRecreation_stateIsRestored() {
        val restorationTester = StateRestorationTester(composeTestRule)

        restorationTester.setContent { MainScreen() }

        // TODO: Run actions that modify the state

        // Trigger a recreation
        restorationTester.emulateSavedInstanceStateRestore()

        // TODO: Verify that state has been correctly restored.
    }
}

Testowanie różnych konfiguracji urządzeń

Aplikacje na Androida muszą dostosowywać się do wielu zmieniających się warunków: rozmiarów okien, ustawień regionalnych, rozmiarów czcionek, ciemnych i jasnych motywów i nie tylko. Większość tych warunków jest określana na podstawie wartości na poziomie urządzenia kontrolowanych przez użytkownika i udostępnianych w bieżącej instancji Configuration. Testowanie różnych konfiguracji bezpośrednio w teście jest trudne, ponieważ test musi konfigurować właściwości na poziomie urządzenia.

DeviceConfigurationOverride to interfejs API przeznaczony tylko do testów, który umożliwia symulowanie różnych konfiguracji urządzeń w sposób zlokalizowany na potrzeby testowanej treści @Composable.

Obiekt towarzyszący DeviceConfigurationOverride ma te funkcje rozszerzające, które zastępują właściwości konfiguracji na poziomie urządzenia:

Aby zastosować konkretne zastąpienie, umieść testowaną treść w wywołaniu funkcji najwyższego poziomu DeviceConfigurationOverride(), przekazując zastąpienie do zastosowania jako parametr.

Na przykład poniższy kod stosuje zastąpienie DeviceConfigurationOverride.ForcedSize(), aby lokalnie zmienić gęstość, wymuszając renderowanie funkcji kompozycyjnej MyScreen w dużym oknie w orientacji poziomej, nawet jeśli urządzenie, na którym jest uruchamiany test, nie obsługuje bezpośrednio tego rozmiaru okna:

composeTestRule.setContent {
    DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
}

Aby zastosować kilka zastąpień jednocześnie, użyj elementu DeviceConfigurationOverride.then():

composeTestRule.setContent {
    DeviceConfigurationOverride(
        DeviceConfigurationOverride.FontScale(1.5f) then
            DeviceConfigurationOverride.FontWeightAdjustment(200)
    ) {
        Text(text = "text with increased scale and weight")
    }
}

Niestandardowa obsługa błędów w testach

Debugowanie testów interfejsu często wymaga sprawdzenia stanu ekranu i hierarchii kompozycji, gdy nie powiedzie się asercja.

Compose udostępnia natywny potok obsługi błędów, który usprawnia diagnostykę. Możesz automatycznie robić zrzuty ekranu i hierarchii interfejsu w przypadku awarii lub rejestrować niestandardowe moduły obsługi, aby przekazywać dane telemetryczne o awariach do narzędzi zewnętrznych.

Konfigurowanie obsługi błędów

Aby skonfigurować obsługę błędów, przekaż obiekt TestFailurePolicy do funkcji ComposeUiTestConfig.

Przechwytywanie wbudowanych artefaktów błędów

Aby w przypadku niepowodzenia testu przechwytywać zrzuty ekranu i hierarchie interfejsu, ustaw właściwości screenshotCaptureMode i uiHierarchyCaptureMode w TestFailurePolicy.

class MyComposeTest {
    private val customConfig = ComposeUiTestConfig(
        failurePolicy = TestFailurePolicy(
            screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled,
            uiHierarchyCaptureMode = TestFailurePolicy.CaptureMode.Enabled
        )
    )

    @Test
    fun myFirstTest() = runComposeUiTest(config = customConfig) {
        // ...
    }
}

Używanie niestandardowego modułu obsługi błędów

Aby zdefiniować niestandardowe zachowanie w przypadku niepowodzenia testu, dodaj element TestFailureHandler do failureHandlers listy. Każdy moduł obsługi otrzymuje obiekt FailureContext, który zawiera pierwotny błąd i listę artefaktów wygenerowanych przez poprzednie moduły obsługi potoku.

class MyComposeTestWithHandler {
    private val customConfig = ComposeUiTestConfig(
        failurePolicy = TestFailurePolicy(
            screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled,
            failureHandlers = listOf(
                TestFailureHandler { context ->
                    val screenshot = context.artifacts.firstOrNull {
                        it.type == FailureArtifact.Type.Screenshot
                    }
                    // ...
                }
            )
        )
    )

    @get:Rule
    val rule = createComposeRule(config = customConfig)

    // ...
}

Konfigurowanie przechwytywania dla całego zestawu testów

Możesz włączyć przechwytywanie artefaktów błędów dla całego zestawu testów, przekazując argumenty narzędzia do uruchamiania testów w pliku build.gradle modułu. Eliminuje to konieczność aktualizowania poszczególnych plików testowych.

// build.gradle.kts
android {
    defaultConfig {
        // ...
        testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isUiHierarchyCaptureEnabled"] = "true"
        testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isScreenshotCaptureEnabled"] = "true"
    }
}

Pierwszeństwo konfiguracji

Framework ocenia zarówno argumenty lokalne TestFailurePolicy, jak i argumenty na poziomie pakietu:

  • Jeśli w teście failurePolicy wyraźnie ustawisz tryb na CaptureMode.Enabled/Disabled, zastąpi to argument wykonawcy.
  • Jeśli ustawisz tryb na CaptureMode.Unspecified (lub pozostawisz failurePolicy jakonull), platforma powróci do argumentu wykonawcy.

Dodatkowe materiały

  • Testowanie aplikacji na Androida: główna strona docelowa dotycząca testowania na Androidzie zawiera szersze omówienie podstaw i technik testowania.
  • Podstawy testowania: dowiedz się więcej o podstawowych koncepcjach związanych z testowaniem aplikacji na Androida.
  • Testy lokalne: niektóre testy możesz przeprowadzać lokalnie, na własnej stacji roboczej.
  • Testy z użyciem instrumentacji: warto też przeprowadzać testy z użyciem instrumentacji. Chodzi o testy, które są przeprowadzane bezpośrednio na urządzeniu.
  • Tryb ciągłej integracji: Tryb ciągłej integracji umożliwia zintegrowanie testów z potokiem wdrażania.
  • Testowanie różnych rozmiarów ekranu: użytkownicy mają do dyspozycji wiele urządzeń, dlatego warto przeprowadzić testy na różnych rozmiarach ekranu.
  • Espresso: chociaż Espresso jest przeznaczony do interfejsów opartych na widokach, wiedza o nim może być przydatna w przypadku niektórych aspektów testowania Compose.