Vous pouvez tester votre application Compose à l'aide d'approches et de modèles bien établis.
Effectuer un test isolé
ComposeTestRule vous permet de lancer une activité affichant n'importe quel composable : votre application complète, un seul écran ou un petit élément. Il est également recommandé de vérifier que vos composables sont correctement encapsulés et fonctionnent de manière autonome afin de tester l'interface utilisateur plus facilement et plus précisément.
Cela ne signifie pas que vous devez uniquement créer des tests d'interface utilisateur. Il est également très important de tester la portée de plus grandes parties de votre interface utilisateur.
Accédez à l'activité et aux ressources après avoir défini votre propre contenu
Vous devez souvent configurer le contenu testé à l'aide de composeTestRule.setContent et accéder aux ressources d'activité, par exemple pour affirmer qu'un texte affiché correspond à une ressource de chaîne. Toutefois, vous ne pouvez pas appeler setContent sur une règle créée avec createAndroidComposeRule() si l'activité l'appelle déjà.
Pour ce faire, il est courant de créer une AndroidComposeTestRule à l'aide d'une activité vide (comme 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()
}
}
Notez que ComponentActivity doit être ajouté au fichier AndroidManifest.xml de votre application. Pour ce faire, ajoutez la dépendance suivante à votre module :
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
Propriétés de la sémantique personnalisée
Vous pouvez créer des propriétés sémantiques personnalisées pour soumettre des informations aux tests.
Pour ce faire, définissez une nouvelle SemanticsPropertyKey et rendez-la disponible à l'aide de SemanticsPropertyReceiver.
// Creates a semantics property of type Long.
val PickedDateKey = SemanticsPropertyKey<Long>("PickedDate")
var SemanticsPropertyReceiver.pickedDate by PickedDateKey
Utilisez maintenant cette propriété dans le modificateur semantics :
val datePickerValue by remember { mutableStateOf(0L) }
MyCustomDatePicker(
modifier = Modifier.semantics { pickedDate = datePickerValue }
)
À partir des tests, utilisez SemanticsMatcher.expectValue pour réclamer la valeur de la propriété :
composeTestRule
.onNode(SemanticsMatcher.expectValue(PickedDateKey, 1445378400)) // 2015-10-21
.assertExists()
Vérifier la restauration d'état
Vérifiez que l'état de vos éléments Compose est correctement restauré lorsque l'activité ou le processus est recréé. Effectuez ces vérifications sans dépendre de la recréation d'activité avec la classe StateRestorationTester.
Cette classe vous permet de simuler la recréation d'un composable. Elle s'avère particulièrement utile pour vérifier la mise en œuvre de 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.
}
}
Tester différentes configurations d'appareils
Les applications Android doivent s'adapter à de nombreuses conditions changeantes : tailles de fenêtre, paramètres régionaux, tailles de police, thèmes sombres et clairs, etc. La plupart de ces conditions sont dérivées de valeurs au niveau de l'appareil contrôlées par l'utilisateur et exposées avec l'instance Configuration actuelle. Il est difficile de tester différentes configurations directement dans un test, car celui-ci doit configurer des propriétés au niveau de l'appareil.
DeviceConfigurationOverride est une API réservée aux tests qui vous permet de simuler différentes configurations d'appareils de manière localisée pour le contenu @Composable testé.
L'objet compagnon de DeviceConfigurationOverride comporte les fonctions d'extension suivantes, qui remplacent les propriétés de configuration au niveau de l'appareil :
DeviceConfigurationOverride.DarkMode(): remplace le thème du système par le thème sombre ou clair.DeviceConfigurationOverride.FontScale(): remplace l'échelle de police du système.DeviceConfigurationOverride.FontWeightAdjustment(): remplace l'ajustement de l'épaisseur de la police système.DeviceConfigurationOverride.ForcedSize(): force une quantité d'espace spécifique, quelle que soit la taille de l'appareil.DeviceConfigurationOverride.LayoutDirection(): remplace la direction de la mise en page (de gauche à droite ou de droite à gauche).DeviceConfigurationOverride.Locales(): remplace les paramètres régionaux.DeviceConfigurationOverride.RoundScreen(): remplace si l'écran est rond.
Pour appliquer un remplacement spécifique, encapsulez le contenu testé dans un appel à la fonction de niveau supérieur DeviceConfigurationOverride(), en transmettant le remplacement à appliquer en tant que paramètre.
Par exemple, le code suivant applique le remplacement DeviceConfigurationOverride.ForcedSize() pour modifier la densité localement, ce qui force l'affichage du composable MyScreen dans une grande fenêtre en mode paysage, même si l'appareil sur lequel le test est exécuté n'est pas directement compatible avec cette taille de fenêtre :
composeTestRule.setContent { DeviceConfigurationOverride( DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp)) ) { MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping. } }
Pour appliquer plusieurs remplacements ensemble, utilisez DeviceConfigurationOverride.then() :
composeTestRule.setContent { DeviceConfigurationOverride( DeviceConfigurationOverride.FontScale(1.5f) then DeviceConfigurationOverride.FontWeightAdjustment(200) ) { Text(text = "text with increased scale and weight") } }
Gestion personnalisée des échecs dans les tests
Le débogage des tests d'interface utilisateur nécessite souvent d'inspecter l'état de l'écran et la hiérarchie de composition lorsqu'une assertion échoue.
Compose fournit un pipeline de gestion des échecs natif pour simplifier les diagnostics. Vous pouvez capturer automatiquement des captures d'écran et des hiérarchies d'UI en cas d'échec, ou enregistrer des gestionnaires personnalisés pour transférer les données de télémétrie des plantages vers des outils externes.
Configurer la gestion des échecs
Pour configurer la gestion des échecs, transmettez un objet TestFailurePolicy à ComposeUiTestConfig.
Capturer les artefacts d'échec intégrés
Pour capturer des captures d'écran et des hiérarchies d'UI en cas d'échec du test, définissez les propriétés screenshotCaptureMode et uiHierarchyCaptureMode dans TestFailurePolicy.
class MyComposeTest { private val customConfig = ComposeUiTestConfig( failurePolicy = TestFailurePolicy( screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled, uiHierarchyCaptureMode = TestFailurePolicy.CaptureMode.Enabled ) ) @Test fun myFirstTest() = runComposeUiTest(config = customConfig) { // ... } }
Utiliser un gestionnaire d'échecs personnalisé
Pour définir un comportement personnalisé en cas d'échec des tests, ajoutez un TestFailureHandler à la liste failureHandlers. Chaque gestionnaire reçoit un objet FailureContext contenant l'erreur d'origine et une liste d'artefacts générés par les gestionnaires de pipeline précédents.
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) // ... }
Configurer des captures pour une suite de tests entière
Vous pouvez activer la capture d'artefacts d'échec pour l'ensemble d'une suite de tests en transmettant des arguments d'exécuteur d'instrumentation dans le fichier build.gradle de votre module. Cela évite d'avoir à mettre à jour des fichiers de test individuels.
// build.gradle.kts
android {
defaultConfig {
// ...
testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isUiHierarchyCaptureEnabled"] = "true"
testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isScreenshotCaptureEnabled"] = "true"
}
}
Priorité de configuration
Le framework évalue à la fois les arguments de l'exécuteur TestFailurePolicy local et ceux de l'exécuteur au niveau de la suite :
- Si vous définissez explicitement un mode sur
CaptureMode.Enabled/Disableddans votrefailurePolicyde test, cela remplace l'argument de l'exécuteur. - Si vous définissez un mode sur
CaptureMode.Unspecified(ou si vous laissezfailurePolicysurnull), le framework utilise l'argument de l'exécuteur.
Autres ressources
- Tester des applications sur Android : la page de destination principale des tests Android offre une vue plus large des principes de base et des techniques de test.
- Principes fondamentaux des tests : découvrez les concepts de base des tests d'une application Android.
- Tests locaux : vous pouvez exécuter certains tests localement, sur votre propre poste de travail.
- Tests d'instrumentation : il est également recommandé d'exécuter des tests d'instrumentation. C'est-à-dire des tests qui s'exécutent directement sur l'appareil.
- Intégration continue : l'intégration continue vous permet d'intégrer vos tests dans votre pipeline de déploiement.
- Tester différentes tailles d'écran : étant donné le grand nombre d'appareils disponibles pour les utilisateurs, vous devez tester différentes tailles d'écran.
- Espresso : bien qu'il soit destiné aux UI basées sur les vues, Espresso peut toujours être utile pour certains aspects des tests Compose.