Tester l'application Cupcake

1. Introduction

Dans l'atelier de programmation Naviguer entre les écrans avec Compose, vous avez appris à ajouter une navigation à une application Compose à l'aide du composant Navigation de Jetpack.

L'application Cupcake propose plusieurs écrans de navigation et différentes actions que l'utilisateur peut effectuer. Elle vous permettra d'affiner vos compétences en matière de tests automatisés. Dans cet atelier de programmation, vous écrirez plusieurs tests d'interface utilisateur pour l'application Cupcake et découvrirez comment maximiser la couverture de test.

Conditions préalables

  • Vous maîtrisez le langage Kotlin, y compris les types de fonctions, les expressions lambda et les fonctions de champ d'application.
  • Vous avez suivi l'atelier de programmation Naviguer entre les écrans avec Compose.

Points abordés

  • Tester le composant Navigation de Jetpack avec Compose
  • Créez un état d'UI cohérent pour chaque test d'interface utilisateur
  • Créer des fonctions d'assistance pour les tests

Objectifs de l'atelier

  • Tests de l'interface utilisateur pour l'application Cupcake

Ce dont vous avez besoin

  • La dernière version d'Android Studio
  • Une connexion Internet pour télécharger le code de démarrage

2. Télécharger le code de démarrage

  1. Dans Android Studio, ouvrez le dossier basic-android-kotlin-compose-training-cupcake.
  2. Ouvrez le code de l'application Cupcake dans Android Studio.

3. Configurer Cupcake pour les tests de l'interface utilisateur

Ajouter les dépendances androidTest

L'outil de compilation Gradle vous permet d'ajouter des dépendances pour des modules spécifiques. Cette fonctionnalité empêche la compilation superflue des dépendances. Vous connaissez déjà la configuration de l'élément implementation lorsque vous incluez des dépendances dans un projet. Vous avez utilisé ce mot clé pour importer des dépendances dans le fichier build.gradle.kts du module de l'application. L'utilisation du mot clé implementation rend cette dépendance disponible pour tous les ensembles de sources de ce module. À ce stade, vous savez utiliser les ensembles de sources main, test et androidTest.

Les tests d'interface utilisateur sont contenus dans leurs propres ensembles de sources, appelés androidTest. Les dépendances qui sont uniquement nécessaires pour ce module n'ont pas besoin d'être compilées pour les autres, tels que le module main, qui contient le code de l'application. Lorsque vous ajoutez une dépendance qui n'est utilisée que par des tests d'interface utilisateur, optez pour le mot clé androidTestImplementation pour déclarer la dépendance dans le fichier build.gradle.kts du module de l'application. Cette approche permet de s'assurer que les dépendances de test de l'interface utilisateur ne sont compilées que lorsque vous exécutez des tests de l'interface utilisateur.

Pour ajouter les dépendances nécessaires à l'écriture de tests d'interface utilisateur, procédez comme suit :

  1. Ouvrez le fichier build.gradle.kts(Module :app).
  2. Ajoutez les dépendances suivantes à la section dependencies du fichier :
androidTestImplementation(platform("androidx.compose:compose-bom:2023.05.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("androidx.navigation:navigation-testing:2.6.0")
androidTestImplementation("androidx.test.espresso:espresso-intents:3.5.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")

Créer le répertoire de test de l'interface utilisateur

  1. Effectuez un clic droit sur le répertoire src dans la vue du projet, puis sélectionnez Nouveau > Répertoire.

31f950996e51807b.png

  1. Sélectionnez l'option androidTest/java.

e1e4d108c863ae27.png

Créer le package de test

  1. Effectuez un clic droit sur le répertoire androidTest/java dans la fenêtre du projet, puis sélectionnez Nouveau > Package.

c5cdd7125e61bf22.png

  1. Nommez le package com.example.cupcake.test. b6cec61624467422.png

Créer la classe de test de navigation

Dans le répertoire test, créez une classe Kotlin nommée CupcakeScreenNavigationTest.

8da2547b2ef1cc8e.png

4. Configurer l'hôte de navigation

Dans un précédent atelier de programmation, vous avez découvert que les tests d'interface utilisateur dans Compose nécessitent une règle de test Compose. Il en va de même pour le composant Navigation de Jetpack. Toutefois, les tests de navigation nécessitent une configuration supplémentaire via la règle de test Compose.

Lorsque vous testez la navigation Compose, vous n'avez pas accès au même élément NavHostController que dans le code de l'application. Toutefois, vous pouvez utiliser un élément TestNavHostController et configurer la règle de test avec ce contrôleur de navigation. Dans cette section, vous apprendrez à configurer et à réutiliser la règle de test pour les tests de navigation.

  1. Dans CupcakeScreenNavigationTest.kt, créez une règle de test en utilisant createAndroidComposeRule et en transmettant ComponentActivity comme paramètre de type.
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import org.junit.Rule

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

Pour vous assurer que l'application accède au bon endroit, vous devez référencer une instance TestNavHostController afin de vérifier l'itinéraire de navigation de l'hôte de navigation lorsque l'application passe d'un écran à un autre, par exemple.

  1. Instanciez une instance TestNavHostController en tant que variable lateinit. En langage Kotlin, le mot clé lateinit permet de déclarer une propriété qui peut être initialisée après la déclaration de l'objet.
import androidx.navigation.testing.TestNavHostController

private lateinit var navController: TestNavHostController

Spécifiez ensuite le composable que vous souhaitez utiliser pour les tests de l'UI.

  1. Créez une méthode appelée setupCupcakeNavHost().
  2. Dans la méthode setupCupcakeNavHost(), appelez la méthode setContent() au niveau de la règle de test Compose que vous avez créée.
  3. Dans le lambda transmis à la méthode setContent(), appelez le composable CupcakeApp().
import com.example.cupcake.CupcakeApp

fun setupCupcakeNavHost() {
    composeTestRule.setContent {
        CupcakeApp()
    }
}

Vous devez maintenant créer l'objet TestNavHostContoller dans la classe de test. Vous utiliserez cet objet plus tard pour déterminer l'état de navigation, car l'application utilise le contrôleur pour parcourir les différents écrans de l'application Cupcake.

  1. Configurez l'hôte de navigation à l'aide du lambda que vous avez créé précédemment. Initialisez la variable navController que vous avez créée, enregistrez un navigateur et transmettez cet élément TestNavHostController au composable CupcakeApp.
import androidx.compose.ui.platform.LocalContext

fun setupCupcakeNavHost() {
    composeTestRule.setContent {
        navController = TestNavHostController(LocalContext.current).apply {
            navigatorProvider.addNavigator(ComposeNavigator())
        }
        CupcakeApp(navController = navController)
    }
}

Chaque test appartenant à la classe CupcakeScreenNavigationTest implique de tester un aspect de la navigation. Par conséquent, chaque test dépend de l'objet TestNavHostController que vous avez créé. Au lieu d'appeler manuellement la fonction setupCupcakeNavHost() pour chaque test afin de configurer le contrôleur de navigation, vous pouvez procéder automatiquement à l'aide de l'annotation @Before fournie par la bibliothèque junit. Lorsqu'une méthode est annotée avec @Before, elle s'exécute avant chaque méthode annotée avec @Test.

  1. Ajoutez l'annotation @Before à la méthode setupCupcakeNavHost().
import org.junit.Before

@Before
fun setupCupcakeNavHost() {
    composeTestRule.setContent {
        navController = TestNavHostController(LocalContext.current).apply {
            navigatorProvider.addNavigator(ComposeNavigator())
        }
        CupcakeApp(navController = navController)
    }
}

5. Écrire les tests de navigation

Vérifier la destination de départ

Rappelez-vous que, lorsque vous avez créé l'application Cupcake, vous avez conçu une classe enum appelée CupcakeScreen qui contenait des constantes permettant de dicter la navigation dans l'application.

CupcakeScreen.kt

/**
* enum values that represent the screens in the app
*/
enum class CupcakeScreen(@StringRes val title: Int) {
   Start(title = R.string.app_name),
   Flavor(title = R.string.choose_flavor),
   Pickup(title = R.string.choose_pickup_date),
   Summary(title = R.string.order_summary)
}

Toutes les applications disposant d'une UI ont un écran d'accueil. Pour Cupcake, il s'agit de l'écran Start Order (Lancer la commande). Le contrôleur de navigation du composable CupcakeApp utilise l'élément Start de l'énumération CupcakeScreen pour déterminer quand accéder à cet écran. Lorsque l'application démarre, s'il n'existe pas d'itinéraire pour accéder à la destination, elle est définie sur CupcakeScreen.Start.name.

Vous devez d'abord écrire un test pour vérifier que l'écran de lancement de la commande correspond à l'itinéraire de destination actuel lorsque l'application démarre.

  1. Créez une fonction appelée cupcakeNavHost_verifyStartDestination() et annotez-la avec @Test.
import org.junit.Test

@Test
fun cupcakeNavHost_verifyStartDestination() {
}

Vous devez maintenant confirmer que l'itinéraire de destination initial du contrôleur de navigation est l'écran de lancement de la commande.

  1. Déclarez que le nom de l'itinéraire attendu (dans ce cas, CupcakeScreen.Start.name) équivaut à l'itinéraire de destination de l'entrée actuelle de la pile "Retour" du contrôleur de navigation.
import org.junit.Assert.assertEquals
...

@Test
fun cupcakeNavHost_verifyStartDestination() {
    assertEquals(CupcakeScreen.Start.name, navController.currentBackStackEntry?.destination?.route)
}

Créer des méthodes d'assistance

Les tests de l'interface utilisateur requièrent souvent la répétition d'étapes permettant de placer l'interface utilisateur dans un état tel qu'un élément particulier de l'interface peut être testé. Une UI personnalisée peut également nécessiter des assertions complexes nécessitant plusieurs lignes de code. L'assertion que vous avez écrite dans la section précédente nécessite beaucoup de code. Vous l'utiliserez souvent lorsque vous testerez la navigation dans l'application Cupcake. Dans ces situations, l'écriture de méthodes d'assistance dans vos tests vous évite d'avoir à écrire du code en double.

Pour chaque test de navigation que vous écrivez, vous devez utiliser la propriété name correspondant aux éléments d'énumération CupcakeScreen afin de vérifier que l'itinéraire de destination actuel du contrôleur de navigation est correct. Vous écrirez une fonction d'assistance que vous pouvez appeler chaque fois que vous souhaitez effectuer une telle assertion.

Pour créer cette fonction d'assistance, procédez comme suit :

  1. Dans le répertoire test, créez un fichier Kotlin vide nommé ScreenAssertions.

8c3c7146050db2a8.png

  1. Ajoutez une fonction d'extension à la classe NavController appelée assertCurrentRouteName(), puis transmettez une chaîne correspondant au nom d'itinéraire attendu dans la signature de la méthode.
fun NavController.assertCurrentRouteName(expectedRouteName: String) {

}
  1. Dans cette fonction, déclarez que expectedRouteName est égal à l'itinéraire de destination de l'entrée de la pile "Retour" du contrôleur de navigation.
import org.junit.Assert.assertEquals
...

fun NavController.assertCurrentRouteName(expectedRouteName: String) {
    assertEquals(expectedRouteName, currentBackStackEntry?.destination?.route)
}
  1. Ouvrez le fichier CupcakeScreenNavigationTest et modifiez la fonction cupcakeNavHost_verifyStartDestination() afin d'utiliser votre nouvelle fonction d'extension au lieu de la longue assertion.
@Test
fun cupcakeNavHost_verifyStartDestination() {
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

Un certain nombre de tests nécessitent également une interaction avec les composants d'interface utilisateur. Dans cet atelier de programmation, ces composants sont souvent accessibles à l'aide d'une chaîne de ressource. Vous pouvez accéder à un composable par sa chaîne de ressource à l'aide de la méthode Context.getString(). Pour en savoir plus, cliquez ici. Lorsque vous écrivez un test d'interface utilisateur dans Compose, l'implémentation de cette méthode se présente comme suit :

composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.my_string)

Il s'agit d'une instruction détaillée qui peut être simplifiée en ajoutant une fonction d'extension.

  1. Créez un fichier dans le package com.example.cupcake.test nommé ComposeRuleExtensions.kt. Assurez-vous qu'il s'agit d'un fichier Kotlin simple.

82762f66f890cf1b.png

  1. Ajoutez le code suivant à ce fichier :
import androidx.activity.ComponentActivity
import androidx.annotation.StringRes
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.rules.ActivityScenarioRule

fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.onNodeWithStringId(
    @StringRes id: Int
): SemanticsNodeInteraction = onNodeWithText(activity.getString(id))

Cette fonction d'extension vous permet de réduire la quantité de code que vous écrivez lorsque vous recherchez un composant d'interface utilisateur à l'aide de sa ressource de chaîne. Au lieu d'écrire l'instruction :

composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.my_string)

Vous pouvez désormais utiliser l'instruction suivante :

composeTestRule.onNodeWithStringId(R.string.my_string)

Vérifier que l'écran de démarrage ne comporte pas de bouton "Haut"

Le design d'origine de l'application Cupcake ne comporte pas de bouton "Haut" dans la barre d'outils de l'écran de démarrage.

886fb7e824608c92.png

L'écran de démarrage ne comporte pas ce bouton, car il n'existe aucune option permettant de remonter à l'écran précédent étant donné qu'il s'agit de l'écran initial. Pour créer une fonction qui confirme que l'écran de démarrage ne comporte pas de bouton "Haut", procédez comme suit :

  1. Créez une méthode appelée cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() et annotez-la avec @Test.
@Test
fun cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() {
}

Dans Cupcake, la description du contenu du bouton "Haut" est définie sur la chaîne à partir de la ressource R.string.back_button.

  1. Créez une variable dans la fonction de test avec la valeur de la ressource R.string.back_button.
@Test
fun cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() {
    val backText = composeTestRule.activity.getString(R.string.back_button)
}
  1. Indiquez qu'aucun nœud correspondant à cette description de contenu n'apparaît à l'écran.
@Test
fun cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() {
    val backText = composeTestRule.activity.getString(R.string.back_button)
    composeTestRule.onNodeWithContentDescription(backText).assertDoesNotExist()
}

Vérifier la navigation vers l'écran "Flavor" (Parfum)

Lorsque vous cliquez sur l'un des boutons de l'écran de démarrage, une méthode permettant au contrôleur de navigation d'accéder à l'écran "Flavor" (Parfum) est déclenchée.

82c62c13891d627e.png

Dans ce test, vous écrirez une commande grâce à laquelle un clic sur un bouton déclenchera cette navigation et vérifiera que l'itinéraire de destination correspond à l'écran "Flavor" (Parfum).

  1. Créez une fonction appelée cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen() et annotez-la avec @Test.
@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen(){
}
  1. Recherchez le bouton "One Cupcake" (Un cupcake) à côté de son ID de ressource de chaîne et effectuez une action de clic sur celui-ci.
import com.example.cupcake.R
...

@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen() {
    composeTestRule.onNodeWithStringId(R.string.one_cupcake)
        .performClick()
}
  1. Confirmez que le nom actuel de l'itinéraire est le nom de l'écran "Flavor" (Parfum).
@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen() {
    composeTestRule.onNodeWithStringId(R.string.one_cupcake)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Flavor.name)
}

Écrire d'autres méthodes d'assistance

L'application Cupcake propose un flux de navigation essentiellement linéaire. À moins de cliquer sur le bouton "Cancel" (Annuler), vous ne pouvez naviguer que dans une seule direction. Par conséquent, lorsque vous testez des écrans qui se trouvent plus loin dans l'application, vous vous retrouvez à répéter le même code pour accéder aux zones que vous souhaitez tester. Cette situation justifie l'utilisation de davantage de méthodes d'assistance pour ne pas avoir à écrire ce code plusieurs fois.

Maintenant que vous avez testé la navigation vers l'écran "Flavor" (Parfum), créez une méthode permettant d'accéder à cet écran afin de ne pas avoir à répéter ce code pour de futurs tests.

  1. Créez une méthode appelée navigateToFlavorScreen().
private fun navigateToFlavorScreen() {
}
  1. Écrivez une commande permettant de localiser le bouton "One Cupcake" (Un cupcake) et effectuez une action de clic sur celui-ci, comme dans la section précédente.
private fun navigateToFlavorScreen() {
    composeTestRule.onNodeWithStringId(R.string.one_cupcake)
        .performClick()
}

Rappelez-vous que le bouton "Next" (Suivant) de l'écran "Flavor" (Parfum) n'est pas cliquable tant que vous n'avez pas sélectionné un type de produit. Cette méthode sert uniquement à préparer l'interface utilisateur pour la navigation. Une fois que vous avez appelé cette méthode, l'interface utilisateur devrait se trouver dans un état qui permet à l'utilisateur de cliquer sur le bouton "Next" (Suivant).

  1. Recherchez un nœud dans l'interface utilisateur avec la chaîne R.string.chocolate, puis effectuez une action de clic sur celui-ci afin de le sélectionner.
private fun navigateToFlavorScreen() {
    composeTestRule.onNodeWithStringId(R.string.one_cupcake)
        .performClick()
    composeTestRule.onNodeWithStringId(R.string.chocolate)
        .performClick()
}

Essayez d'écrire des méthodes d'assistance qui redirigent vers l'écran "Pickup" (Collecte) et l'écran "Summary" (Résumé). Essayez cet exercice par vous-même avant de consulter la solution.

Pour ce faire, utilisez le code suivant :

private fun getFormattedDate(): String {
    val calendar = Calendar.getInstance()
    calendar.add(java.util.Calendar.DATE, 1)
    val formatter = SimpleDateFormat("E MMM d", Locale.getDefault())
    return formatter.format(calendar.time)
}
private fun navigateToPickupScreen() {
    navigateToFlavorScreen()
    composeTestRule.onNodeWithStringId(R.string.next)
        .performClick()
}

private fun navigateToSummaryScreen() {
    navigateToPickupScreen()
    composeTestRule.onNodeWithText(getFormattedDate())
        .performClick()
    composeTestRule.onNodeWithStringId(R.string.next)
        .performClick()
}

Lorsque vous testez des écrans au-delà de l'écran de démarrage, vous devez tester la fonctionnalité du bouton "Haut" pour vous assurer qu'il renvoie la navigation vers l'écran précédent. Pensez à créer une fonction d'assistance pour localiser le bouton "Haut" et pour cliquer dessus.

private fun performNavigateUp() {
    val backText = composeTestRule.activity.getString(R.string.back_button)
    composeTestRule.onNodeWithContentDescription(backText).performClick()
}

Maximiser la couverture de test

La suite de tests d'une application doit tester autant de fonctionnalités que possible. Dans un monde parfait, une suite de tests d'UI couvrirait 100 % de ses fonctionnalités. En pratique, cette couverture de test est difficile à atteindre, car de nombreux facteurs externes à l'application peuvent affecter l'UI, tels que les appareils dotés de tailles d'écran uniques, les différentes versions du système d'exploitation Android et les applications tierces susceptibles d'affecter d'autres applications sur le téléphone.

Pour maximiser la couverture de test, vous pouvez écrire des tests avec les fonctionnalités à mesure que vous les ajoutez. Vous éviterez ainsi d'aller trop loin dans la création de nouvelles fonctionnalités et d'avoir à revenir en arrière et à vous remémorer tous les scénarios possibles. Cupcake est une application assez petite à ce stade. Vous avez d'ailleurs déjà testé une grande partie de sa navigation. Cependant, d'autres états de navigation sont à tester.

Voyez si vous parvenez à écrire les tests afin de vérifier les états de navigation suivants. Essayez de les implémenter par vous-même avant de vous reporter à la solution.

  • Accéder à l'écran de démarrage en cliquant sur le bouton "Haut" de l'écran "Flavor" (Parfum)
  • Accéder à l'écran de démarrage en cliquant sur le bouton "Cancel" (Annuler) de l'écran "Flavor" (Parfum)
  • Accéder à l'écran "Pickup" (Collecte)
  • Accéder à l'écran "Flavor" (Parfum) en cliquant sur le bouton "Haut" de l'écran "Pickup" (Collecte)
  • Accéder à l'écran de démarrage en cliquant sur le bouton "Cancel" (Annuler) de l'écran "Pickup" (Collecte)
  • Accéder à l'écran "Summary" (Résumé)
  • Accéder à l'écran de démarrage en cliquant sur le bouton "Annuler" de l'écran "Summary" (Résumé)
@Test
fun cupcakeNavHost_clickNextOnFlavorScreen_navigatesToPickupScreen() {
    navigateToFlavorScreen()
    composeTestRule.onNodeWithStringId(R.string.next)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Pickup.name)
}

@Test
fun cupcakeNavHost_clickBackOnFlavorScreen_navigatesToStartOrderScreen() {
    navigateToFlavorScreen()
    performNavigateUp()
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

@Test
fun cupcakeNavHost_clickCancelOnFlavorScreen_navigatesToStartOrderScreen() {
    navigateToFlavorScreen()
    composeTestRule.onNodeWithStringId(R.string.cancel)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

@Test
fun cupcakeNavHost_clickNextOnPickupScreen_navigatesToSummaryScreen() {
    navigateToPickupScreen()
    composeTestRule.onNodeWithText(getFormattedDate())
        .performClick()
    composeTestRule.onNodeWithStringId(R.string.next)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Summary.name)
}

@Test
fun cupcakeNavHost_clickBackOnPickupScreen_navigatesToFlavorScreen() {
    navigateToPickupScreen()
    performNavigateUp()
    navController.assertCurrentRouteName(CupcakeScreen.Flavor.name)
}

@Test
fun cupcakeNavHost_clickCancelOnPickupScreen_navigatesToStartOrderScreen() {
    navigateToPickupScreen()
    composeTestRule.onNodeWithStringId(R.string.cancel)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

@Test
fun cupcakeNavHost_clickCancelOnSummaryScreen_navigatesToStartOrderScreen() {
    navigateToSummaryScreen()
    composeTestRule.onNodeWithStringId(R.string.cancel)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

6. Écrire des tests pour l'écran "Order" (Commander)

La navigation n'est qu'un aspect de la fonctionnalité de l'application Cupcake. L'utilisateur interagit également avec chacun des écrans de l'application. Vous devez vérifier ce qui s'affiche sur ces écrans et vous assurer que les actions qui y sont effectuées génèrent les résultats attendus. L'élément SelectOptionScreen est un élément important de l'application.

Dans cette section, vous écrirez un test pour vérifier que le contenu de cet écran est correctement défini.

Tester le contenu de l'écran "Choose Flavor" (Choisir le parfum)

  1. Créez une classe appelée CupcakeOrderScreenTest dans le répertoire app/src/androidTest, qui contient les autres fichiers de test.

4409b8333eff0ba2.png

  1. Dans cette classe, créez un élément AndroidComposeTestRule.
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
  1. Créez une fonction appelée selectOptionScreen_verifyContent() et annotez-la avec @Test.
@Test
fun selectOptionScreen_verifyContent() {

}

Dans cette fonction, vous définirez le contenu de la règle Compose sur SelectOptionScreen. Cela garantit que le composable SelectOptionScreen se lance directement sans que la navigation soit requise. Cependant, cet écran nécessite deux paramètres : une liste de parfums et un sous-total.

  1. Créez une liste de parfums et un sous-total à transmettre à l'écran.
@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"
}
  1. Définissez le contenu sur le composable SelectOptionScreen à l'aide des valeurs que vous venez de créer.

Notez que cette approche est semblable au lancement d'un composable à partir de MainActivity. La seule différence est que MainActivity appelle le composable CupcakeApp et qu'ici, vous appelez le composable SelectOptionScreen. La possibilité de modifier le composable que vous lancez à partir de setContent() vous permet de lancer des composables spécifiques au lieu d'avoir à exécuter le test explicitement dans l'application pour accéder à la zone que vous souhaitez tester. Cette approche permet d'éviter que le test échoue dans des zones du code qui ne sont pas liées à votre test actuel.

@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"

    // When SelectOptionScreen is loaded
    composeTestRule.setContent {
        SelectOptionScreen(subtotal = subtotal, options = flavors)
    }
}

À ce stade du test, l'application lance le composable SelectOptionScreen. Vous pouvez ensuite interagir avec lui à l'aide d'instructions de test.

  1. Parcourez la liste flavors et assurez-vous que chacun de ses éléments de chaîne est affiché à l'écran.
  2. Utilisez la méthode onNodeWithText() pour rechercher le texte à l'écran, puis la méthode assertIsDisplayed() pour vérifier qu'il s'affiche bien dans l'application.
@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"

    // When SelectOptionScreen is loaded
    composeTestRule.setContent {
        SelectOptionScreen(subtotal = subtotal, options = flavors)
    }

    // Then all the options are displayed on the screen.
    flavors.forEach { flavor ->
        composeTestRule.onNodeWithText(flavor).assertIsDisplayed()
    }
}
  1. En suivant la même technique que celle utilisée pour vérifier que le texte s'affiche bien dans l'application, assurez-vous que l'application affiche la chaîne appropriée de sous-total à l'écran. Recherchez l'ID de ressource R.string.subtotal_price et la valeur correcte de sous-total sur l'écran, puis confirmez que l'application affiche la valeur.
import com.example.cupcake.R
...

@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"

    // When SelectOptionScreen is loaded
    composeTestRule.setContent {
        SelectOptionScreen(subtotal = subtotal, options = flavors)
    }

    // Then all the options are displayed on the screen.
    flavors.forEach { flavor ->
        composeTestRule.onNodeWithText(flavor).assertIsDisplayed()
    }

    // And then the subtotal is displayed correctly.
    composeTestRule.onNodeWithText(
        composeTestRule.activity.getString(
            R.string.subtotal_price,
            subtotal
        )
    ).assertIsDisplayed()
}

Rappelez-vous que le bouton "Next" (Suivant) n'est activé que lorsqu'un élément est sélectionné. Ce test ne vérifie que le contenu de l'écran. La dernière chose à vérifier est que le bouton "Next" (Suivant) est désactivé.

  1. Recherchez le bouton "Next" (Suivant) en suivant la même approche, à savoir en recherchant un nœud par ID de ressource de chaîne. Toutefois, au lieu de vérifier que l'application affiche le nœud, utilisez la méthode assertIsNotEnabled().
@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"

    // When SelectOptionScreen is loaded
    composeTestRule.setContent {
        SelectOptionScreen(subtotal = subtotal, options = flavors)
    }

    // Then all the options are displayed on the screen.
    flavors.forEach { flavor ->
        composeTestRule.onNodeWithText(flavor).assertIsDisplayed()
    }

    // And then the subtotal is displayed correctly.
    composeTestRule.onNodeWithText(
        composeTestRule.activity.getString(
            R.string.subtotal_price,
            subtotal
        )
    ).assertIsDisplayed()

    // And then the next button is disabled
    composeTestRule.onNodeWithStringId(R.string.next).assertIsNotEnabled()
}

Maximiser la couverture de test

Le test du contenu de l'écran "Choose Flavor" (Choisir un parfum) ne teste qu'un seul aspect d'un seul écran. Vous pouvez écrire différents tests pour augmenter la couverture de code. Essayez d'écrire vous-même les tests suivants avant de télécharger le code de la solution.

  • Vérifiez le contenu de l'écran de démarrage.
  • Vérifiez le contenu de l'écran "Summary" (Résumé).
  • Vérifiez que le bouton "Suivant" est activé lorsqu'une option est sélectionnée sur l'écran "Choose Flavor" (Choisir un parfum).

Lorsque vous écrivez vos tests, gardez à l'esprit les fonctions d'assistance susceptibles de réduire la quantité de code que vous aurez à écrire.

7. Télécharger le code de solution

Pour télécharger le code de l'atelier de programmation terminé, utilisez la commande Git suivante :

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-cupcake.git

Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.

Si vous souhaitez visualiser le code de solution, affichez-le sur GitHub.

8. Résumé

Félicitations ! Vous savez maintenant tester le composant Navigation de Jetpack. Vous avez également acquis des compétences fondamentales en écriture de tests d'interface utilisateur, comme l'écriture de méthodes d'assistance réutilisables, l'utilisation de setContent() pour rédiger des tests concis, la configuration de tests avec l'annotation @Before et l'optimisation de la couverture de test. Lorsque vous créerez à nouveau des applications Android, n'oubliez pas de continuer à écrire les tests parallèlement au code des fonctionnalités.