Aplikację Compose możesz przetestować, korzystając ze sprawdzonych metod i wzorców.
Testowanie w izolacji
ComposeTestRule umożliwia rozpoczęcie aktywności, która wyświetla 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 jednak, że należy tworzyć tylko testy jednostkowe interfejsu. Testy interfejsu obejmujące większe części interfejsu są również bardzo ważne.
Uzyskiwanie dostępu 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 aktywności, na przykład aby sprawdzić, czy wyświetlany tekst pasuje do zasobu tekstowego. Nie możesz jednak wywołać metody setContent w regule utworzonej za pomocą createAndroidComposeRule(), jeśli aktywność już ją wywołuje.
Typowym sposobem na osiągnięcie tego celu jest utworzenie AndroidComposeTestRule przy użyciu 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 musisz dodać ComponentActivity do pliku AndroidManifest.xml aplikacji. Aby ją włączyć, dodaj do modułu tę zależność:
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
Niestandardowe właściwości semantyki
Możesz utworzyć niestandardowe właściwości semantyki, aby udostępniać informacje testom.
W tym celu 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 sprawdzić 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 testy bez polegania na odtwarzaniu aktywności, używając klasy StateRestorationTester.
Ta klasa umożliwia symulowanie ponownego tworzenia funkcji kompozycyjnej. Jest to szczególnie przydatne do weryfikacji 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, języków, rozmiarów czcionek, ciemnych i jasnych motywów i innych. Większość z tych warunków pochodzi z wartości na poziomie urządzenia, które są kontrolowane przez użytkownika i udostępniane w ramach 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 wyłącznie do testowania, który umożliwia symulowanie różnych konfiguracji urządzeń w sposób zlokalizowany dla testowanych treści @Composable.
Obiekt towarzyszący DeviceConfigurationOverride ma te funkcje rozszerzeń, które zastępują właściwości konfiguracji na poziomie urządzenia:
DeviceConfigurationOverride.DarkMode(): zastępuje motyw systemowy ciemnym lub jasnym motywem.DeviceConfigurationOverride.FontScale(): zastępuje skalę czcionki systemowej.DeviceConfigurationOverride.FontWeightAdjustment(): zastępuje dostosowanie grubości czcionki systemowej.DeviceConfigurationOverride.ForcedSize(): wymusza określoną ilość miejsca niezależnie od rozmiaru urządzenia.DeviceConfigurationOverride.LayoutDirection(): zastępuje kierunek układu (od lewej do prawej lub od prawej do lewej).DeviceConfigurationOverride.Locales(): zastępuje locale.DeviceConfigurationOverride.RoundScreen(): zastępuje, jeśli ekran jest okrągły.
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 ten kod stosuje zastąpienie DeviceConfigurationOverride.ForcedSize(), aby zmienić gęstość lokalnie, wymuszając renderowanie elementu kompozycyjnego MyScreen w dużym oknie poziomym, nawet jeśli urządzenie, na którym jest przeprowadzany 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 DeviceConfigurationOverride.then():
composeTestRule.setContent { DeviceConfigurationOverride( DeviceConfigurationOverride.FontScale(1.5f) then DeviceConfigurationOverride.FontWeightAdjustment(200) ) { Text(text = "text with increased scale and weight") } }
Niestandardowa obsługa awarii w testach
Debugowanie testów interfejsu często wymaga sprawdzenia stanu ekranu i hierarchii kompozycji, gdy asercja się nie powiedzie.
Compose udostępnia natywny potok obsługi błędów, który usprawnia diagnostykę. Możesz automatycznie rejestrować zrzuty ekranu i hierarchie interfejsu w przypadku awarii lub zarejestrować niestandardowe moduły obsługi, aby przekazywać dane telemetryczne o awariach do narzędzi zewnętrznych.
Konfigurowanie obsługi awarii
Aby skonfigurować obsługę błędów, przekaż obiekt TestFailurePolicy do ComposeUiTestConfig.
Przechwytywanie wbudowanych artefaktów błędów
Aby przechwytywać zrzuty ekranu i hierarchie interfejsu w przypadku niepowodzenia testu, 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 działanie w przypadku niepowodzenia testu, dodaj TestFailureHandler do listy failureHandlers. 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 w całym zestawie testów, przekazując argumenty modułu uruchamiającego instrumentację w pliku build.gradle modułu. Dzięki temu nie trzeba aktualizować 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"
}
}
Kolejność konfiguracji
Platforma ocenia zarówno lokalne argumenty TestFailurePolicy, jak i argumenty modułu uruchamiającego na poziomie pakietu:
- Jeśli w teście
failurePolicyjawnie ustawisz trybCaptureMode.Enabled/Disabled, zastąpi to argument runner. - Jeśli ustawisz tryb na
CaptureMode.Unspecified(lub pozostawiszfailurePolicyjakonull), platforma użyje argumentu runner.
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.