Puoi testare la tua app Compose con approcci e pattern consolidati.
Test in isolamento
ComposeTestRule ti consente di avviare un'attività che mostra qualsiasi elemento componibile:
l'intera applicazione, una singola schermata o un piccolo elemento. Inoltre, è una buona
prassi verificare che i composable siano incapsulati correttamente e funzionino
in modo indipendente, consentendo test dell'interfaccia utente più semplici e mirati.
Ciò non significa che devi creare solo test dell'interfaccia utente delle unità. Anche la definizione dell'ambito dei test dell'interfaccia utente di parti più grandi dell'interfaccia utente è molto importante.
Accedere all'attività e alle risorse dopo aver impostato i propri contenuti
Spesso devi impostare i contenuti in fase di test utilizzando
composeTestRule.setContent e devi anche accedere alle risorse delle attività, ad esempio
per verificare che un testo visualizzato corrisponda a una risorsa stringa. Tuttavia, non puoi
chiamare setContent in una regola creata con createAndroidComposeRule() se
l'attività lo chiama già.
Un pattern comune per ottenere questo risultato è creare un AndroidComposeTestRule utilizzando
un'attività vuota come 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()
}
}
Tieni presente che ComponentActivity deve essere aggiunto al file
AndroidManifest.xml della tua app. Attivalo aggiungendo questa dipendenza al tuo modulo:
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
Proprietà semantiche personalizzate
Puoi creare proprietà semantiche personalizzate per esporre informazioni ai test.
Per farlo, definisci un nuovo SemanticsPropertyKey e rendilo disponibile utilizzando
SemanticsPropertyReceiver.
// Creates a semantics property of type Long.
val PickedDateKey = SemanticsPropertyKey<Long>("PickedDate")
var SemanticsPropertyReceiver.pickedDate by PickedDateKey
Ora utilizza questa proprietà nel modificatore semantics:
val datePickerValue by remember { mutableStateOf(0L) }
MyCustomDatePicker(
modifier = Modifier.semantics { pickedDate = datePickerValue }
)
Dai test, utilizza SemanticsMatcher.expectValue per verificare il valore della proprietà:
composeTestRule
.onNode(SemanticsMatcher.expectValue(PickedDateKey, 1445378400)) // 2015-10-21
.assertExists()
Verificare il ripristino dello stato
Verifica che lo stato degli elementi di Compose venga ripristinato correttamente quando l'attività o il processo viene ricreato. Esegui questi controlli senza fare affidamento sulla ricreazione dell'attività con la classe StateRestorationTester.
Questa classe ti consente di simulare la ricreazione di un componente componibile. È particolarmente
utile per verificare l'implementazione di 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.
}
}
Testare diverse configurazioni del dispositivo
Le app per Android devono adattarsi a molte condizioni in continua evoluzione: dimensioni delle finestre, impostazioni internazionali,
dimensioni dei caratteri, temi scuri e chiari e altro ancora. La maggior parte di queste condizioni
deriva da valori a livello di dispositivo controllati dall'utente ed esposti con l'istanza
Configuration corrente. Testare diverse configurazioni
direttamente in un test è difficile, poiché il test deve configurare le proprietà
a livello di dispositivo.
DeviceConfigurationOverride è un'API solo di test che consente di simulare
diverse configurazioni di dispositivi in modo localizzato per i contenuti @Composable
in fase di test.
L'oggetto complementare di DeviceConfigurationOverride ha le seguenti
funzioni di estensione, che sostituiscono le proprietà di configurazione a livello di dispositivo:
DeviceConfigurationOverride.DarkMode(): esegue l'override del sistema per il tema scuro o chiaro.DeviceConfigurationOverride.FontScale(): esegue l'override della scala del carattere di sistema.DeviceConfigurationOverride.FontWeightAdjustment(): esegue l'override della regolazione dello spessore del carattere di sistema.DeviceConfigurationOverride.ForcedSize(): impone una quantità specifica di spazio indipendentemente dalle dimensioni del dispositivo.DeviceConfigurationOverride.LayoutDirection(): esegue l'override della direzione del layout (da sinistra a destra o da destra a sinistra).DeviceConfigurationOverride.Locales(): esegue l'override delle impostazioni internazionali.DeviceConfigurationOverride.RoundScreen(): esegue l'override se lo schermo è rotondo.
Per applicare un override specifico, racchiudi i contenuti in fase di test in una chiamata alla funzione di primo livello DeviceConfigurationOverride(), passando l'override da applicare come parametro.
Ad esempio, il seguente codice applica l'override
DeviceConfigurationOverride.ForcedSize() per modificare la densità
localmente, forzando il rendering del composable MyScreen in una finestra
orizzontale grande, anche se il dispositivo su cui viene eseguito il test non supporta direttamente le dimensioni della finestra:
composeTestRule.setContent { DeviceConfigurationOverride( DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp)) ) { MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping. } }
Per applicare più override insieme, utilizza
DeviceConfigurationOverride.then():
composeTestRule.setContent { DeviceConfigurationOverride( DeviceConfigurationOverride.FontScale(1.5f) then DeviceConfigurationOverride.FontWeightAdjustment(200) ) { Text(text = "text with increased scale and weight") } }
Gestione personalizzata degli errori nei test
Il debug dei test dell'interfaccia utente spesso richiede l'ispezione dello stato dello schermo e della gerarchia di composizione quando un'asserzione non va a buon fine.
Compose fornisce una pipeline di gestione degli errori nativa per semplificare la diagnostica. Puoi acquisire automaticamente screenshot e gerarchie dell'interfaccia utente in caso di errore oppure registrare gestori personalizzati per inoltrare la telemetria degli arresti anomali a strumenti esterni.
Configura la gestione degli errori
Per configurare la gestione degli errori, passa un oggetto TestFailurePolicy a
ComposeUiTestConfig.
Acquisire artefatti di errore integrati
Per acquisire screenshot e gerarchie della UI in caso di test non riuscito, imposta le proprietà
screenshotCaptureMode e uiHierarchyCaptureMode in
TestFailurePolicy.
class MyComposeTest { private val customConfig = ComposeUiTestConfig( failurePolicy = TestFailurePolicy( screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled, uiHierarchyCaptureMode = TestFailurePolicy.CaptureMode.Enabled ) ) @Test fun myFirstTest() = runComposeUiTest(config = customConfig) { // ... } }
Utilizzare un gestore degli errori personalizzato
Per definire un comportamento personalizzato per gli errori di test, aggiungi un
TestFailureHandler all'elenco failureHandlers. Ogni gestore
riceve un oggetto FailureContext che contiene l'errore originale e un elenco di artefatti generati dai gestori della pipeline precedenti.
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) // ... }
Configurare le acquisizioni per un'intera suite di test
Puoi abilitare l'acquisizione degli artefatti di errore per un'intera suite di test passando
gli argomenti del runner di strumentazione nel file build.gradle del modulo. In questo modo
non è necessario aggiornare i singoli file di test.
// build.gradle.kts
android {
defaultConfig {
// ...
testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isUiHierarchyCaptureEnabled"] = "true"
testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isScreenshotCaptureEnabled"] = "true"
}
}
Precedenza della configurazione
Il framework valuta sia gli argomenti del runner a livello locale TestFailurePolicy sia a livello di suite:
- Se imposti esplicitamente una modalità su
CaptureMode.Enabled/Disablednel testfailurePolicy, questa impostazione sostituisce l'argomento del runner. - Se imposti una modalità su
CaptureMode.Unspecified(o lascifailurePolicycomenull), il framework torna all'argomento del runner.
Risorse aggiuntive
- Testare le app su Android: la pagina di destinazione principale dei test Android offre una visione più ampia dei concetti fondamentali e delle tecniche di test.
- Concetti fondamentali sul test di app Android: scopri di più sui concetti di base alla base del test di un'app per Android.
- Test locali: puoi eseguire alcuni test localmente, sulla tua workstation.
- Test strumentati: è buona norma eseguire anche test strumentati. ovvero test eseguiti direttamente sul dispositivo.
- Integrazione continua: l'integrazione continua ti consente di integrare i test nella pipeline di deployment.
- Testa diverse dimensioni dello schermo: con molti dispositivi a disposizione degli utenti, devi eseguire test per diverse dimensioni dello schermo.
- Espresso: sebbene sia destinato alle UI basate sulla visualizzazione, la conoscenza di Espresso può comunque essere utile per alcuni aspetti dei test di Compose.