1. Avant de commencer
Dans les précédents ateliers de programmation, vous avez découvert la navigation à l'aide du composant Navigation. Dans cet atelier de programmation, vous allez apprendre à tester le composant Navigation. Cette approche diffère du test de navigation standard.
Conditions préalables
- Vous avez créé des répertoires de test dans Android Studio.
- Vous avez écrit des tests unitaires et des tests d'instrumentation dans Android Studio.
- Vous avez ajouté des dépendances Gradle à un projet Android.
Points abordés
- Utiliser les tests d'instrumentation pour tester le composant Navigation
- Configurer des tests sans répéter de code
Ce dont vous avez besoin
- Un ordinateur sur lequel est installé Android Studio
- Le code de solution de l'application Words
Télécharger le code de démarrage pour cet atelier de programmation
Dans cet atelier de programmation, vous allez ajouter des tests d'instrumentation au code de solution pour l'application Words.
- Accédez à la page du dépôt GitHub fournie pour le projet.
- Vérifiez que le nom de la branche correspond à celui spécifié dans l'atelier de programmation. Par exemple, dans la capture d'écran suivante, le nom de la branche est main.
- Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.
- Dans la fenêtre pop-up, cliquez sur le bouton Download ZIP (Télécharger le fichier ZIP) pour enregistrer le projet sur votre ordinateur. Attendez la fin du téléchargement.
- Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
- Double-cliquez sur le fichier ZIP pour le décompresser. Un dossier contenant les fichiers du projet est alors créé.
Ouvrir le projet dans Android Studio
- Lancez Android Studio.
- Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open (Ouvrir).
Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).
- Dans l'explorateur de fichiers, accédez à l'emplacement du dossier du projet décompressé (il se trouve probablement dans le dossier Téléchargements).
- Double-cliquez sur le dossier de ce projet.
- Attendez qu'Android Studio ouvre le projet.
- Cliquez sur le bouton Run (Exécuter) pour créer et exécuter l'application. Assurez-vous qu'elle fonctionne correctement.
2. Présentation de l'application de démarrage
L'application Words se compose d'un écran d'accueil qui affiche une liste dont chaque élément est une lettre de l'alphabet. Cliquez sur une lettre pour accéder à un écran contenant une liste de mots commençant par cette lettre.
3. Créer les répertoires de test
Si nécessaire, créez un répertoire de test d'instrumentation pour l'application Word comme vous l'avez fait lors des précédents ateliers de programmation. Si vous avez déjà effectué cette opération, vous pouvez passer à l'étape "Ajouter les dépendances requises".
4. Créer une classe de test d'instrumentation
Créez une classe appelée NavigationTests.kt dans le dossier androidTest.
5. Ajouter les dépendances requises
Le test du composant Navigation nécessite des dépendances Gradle spécifiques. Nous allons également inclure une dépendance permettant de tester des fragments de manière très spécifique. Accédez au fichier build.gradle du module d'application et ajoutez la dépendance suivante :
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.4.0'
androidTestImplementation 'androidx.navigation:navigation-testing:2.5.2'
debugImplementation 'androidx.fragment:fragment-testing:1.5.3'
Maintenant, synchronisez votre projet.
6. Écrire le test du composant Navigation
Le test du composant Navigation diffère du test de navigation standard. Lorsque nous testons la navigation standard, nous déclenchons l'exécution de la navigation sur l'appareil ou dans l'émulateur. Lorsque nous testons le composant Navigation, nous ne déclenchons aucune navigation visible sur l'appareil ou dans l'émulateur. Au lieu de cela, nous forçons le contrôleur de navigation à naviguer sans modifier ce qui s'affiche sur l'appareil ou l'émulateur, puis nous vérifions que le contrôleur est arrivé à la bonne destination.
- Créez une fonction de test appelée
navigate_to_words_nav_component()
. - L'utilisation du composant Navigation dans les tests nécessite une configuration spécifique. Dans la méthode
navigate_to_words_nav_component()
, créez une instance de test du contrôleur de navigation.
val navController = TestNavHostController(
ApplicationProvider.getApplicationContext()
)
- Le composant Navigation pilote l'interface utilisateur à l'aide de fragments. Un équivalent de fragment de
ActivityScenarioRule
peut vous permettre d'isoler un fragment à des fins de test. C'est la raison pour laquelle vous avez besoin d'une dépendance propre au fragment. Cela peut s'avérer très utile pour tester un fragment qui nécessite une part importante de navigation, car il peut être lancé sans code supplémentaire pour gérer la navigation.
val letterListScenario = launchFragmentInContainer<LetterListFragment>(themeResId =
R.style.Theme_Words)
Ici, nous allons spécifier le lancement de LetterListFragment
. Nous devons transmettre le thème de l'application afin que le composant d'UI identifie le thème à utiliser. Dans le cas contraire, le test peut planter.
- Enfin, nous devons déclarer explicitement le graphique de navigation que le contrôleur de navigation doit utiliser pour le fragment lancé.
letterListScenario.onFragment { fragment ->
navController.setGraph(R.navigation.nav_graph)
Navigation.setViewNavController(fragment.requireView(), navController)
}
- Déclenchez à présent l'événement qui déclenche la navigation.
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions
.actionOnItemAtPosition<RecyclerView.ViewHolder>(2, click()))
Lorsque vous utilisez la méthode launchFragmentInContainer()
, la navigation réelle n'est pas possible, car le conteneur n'a pas connaissance des autres fragments ou activités de destination. Il ne connaît que le fragment que nous avons spécifié pour le lancement. Par conséquent, lorsque vous exécutez ce test sur un appareil ou un émulateur, aucune navigation réelle ne s'affiche. Cette méthode peut sembler peu intuitive, mais elle permet de déterminer la destination actuelle de manière beaucoup plus directe. Au lieu de rechercher un composant d'interface utilisateur que l'on sait être affiché sur un écran particulier, nous pouvons vérifier que la destination du contrôleur de navigation actuel possède l'ID de fragment que nous attendons. Cette approche est bien plus fiable que celle mentionnée précédemment.
assertEquals(navController.currentDestination?.id, R.id.wordListFragment)
Votre test doit se présenter comme suit :
7. Code de solution
8. Éviter de répéter le code avec des annotations
Dans Android, les tests d'instrumentation et les tests unitaires comportent une fonctionnalité qui nous permet de définir la même configuration pour chaque test d'une classe sans avoir à répéter le code.
Prenons l'exemple d'un fragment comportant 10 boutons. Chaque bouton mène à un fragment unique à chaque clic de l'utilisateur.
Si nous avions suivi le schéma décrit dans le test ci-dessus, nous devrions répéter un code semblable à celui-ci pour chacun des 10 tests. Veuillez noter que ce code est présenté uniquement à titre d'exemple : il ne sera pas compilé dans l'application avec laquelle nous avons travaillé dans cet atelier de programmation.
val navController = TestNavHostController(
ApplicationProvider.getApplicationContext()
)
val exampleFragmentScenario = launchFragmentInContainer<ExampleFragment>(themeResId =
R.style.Theme_Example)
exampleFragmentScenario.onFragment { fragment ->
navController.setGraph(R.navigation.example_nav_graph)
Navigation.setViewNavController(fragment.requireView(), navController)
}
Répéter 10 fois le même code est fastidieux. Dans ce cas, nous pouvons gagner du temps en utilisant l'annotation @Before
fournie par JUnit. Pour ce faire, nous annotons une méthode dans laquelle nous fournissons le code nécessaire à la configuration de notre test. Nous pouvons nommer la méthode à notre guise, mais de façon pertinente. Au lieu de configurer 10 fois le même fragment, nous pouvons écrire le code de configuration une seule fois de la façon suivante :
lateinit var navController: TestNavHostController
lateinit var exampleFragmentScenario: FragmentScenario<ExampleFragment>
@Before
fun setup(){
navController = TestNavHostController(
ApplicationProvider.getApplicationContext()
)
exampleFragmentScenario = launchFragmentInContainer(themeResId=R.style.Theme_Example)
exampleFragmentScenario.onFragment { fragment ->
navController.setGraph(R.navigation.example_nav_graph)
Navigation.setViewNavController(fragment.requireView(), navController)
}
}
Cette méthode s'exécute désormais pour tous les tests que nous écrivons dans cette classe. Nous pouvons accéder aux variables nécessaires à partir de n'importe quel test.
De même, si vous devez exécuter du code après chaque test, vous pouvez utiliser l'annotation @After
. Par exemple, @After
peut être utilisé pour nettoyer une ressource utilisée pour notre test ou pour des tests d'instrumentation, afin de rétablir un état spécifique de l'appareil.
JUnit fournit également les annotations @BeforeClass
et @AfterClass
. Ici, la différence réside dans le fait que les méthodes comportant cette annotation s'exécutent une fois, mais le code exécuté s'applique toujours à chaque méthode. Si vos méthodes de configuration ou de suppression incluent des opérations coûteuses, il faudra plutôt utiliser ces annotations. Les méthodes annotées avec @BeforeClass
et @AfterClass
doivent être placées dans un objet associé et annotées avec @JvmStatic
. Pour illustrer l'ordre d'exécution de ces annotations, examinons le code suivant :
N'oubliez pas que @BeforeClass
s'exécute pour la classe, @Before
avant les fonctions, @After
après les fonctions et @AfterClass
pour la classe. Pouvez-vous prédire le résultat ?
Voici l'ordre d'exécution des fonctions : setupClass()
, setupFunction()
, test_a()
, tearDownFunction()
, setupFunction()
, test_b()
, tearDownFunction()
, setupFunction()
, test_c()
, tearDownFunction()
, tearDownClass()
. C'est logique, car @Before
et @After
s'exécutent respectivement avant et après chaque méthode. @BeforeClass
s'exécute une fois avant tous les autres éléments de la classe, et @AfterClass
s'exécute après tous les autres éléments de la classe.
9. Félicitations !
Dans cet atelier de programmation, vous avez découvert :
- comment tester le composant Navigation ;
- comment éviter de répéter le code avec les annotations
@Before
,@BeforeClass
,@After
et@AfterClass
.