Tester les ViewModels et LiveData

1. Avant de commencer

Dans les précédents ateliers de programmation, vous avez appris à utiliser des ViewModels pour gérer la logique métier, ainsi que LiveData pour les UI réactives. Dans cet atelier de programmation, vous apprendrez à écrire des tests unitaires pour vérifier que votre code ViewModel fonctionne correctement.

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

  • Écrire des tests unitaires pour les ViewModels et LiveData

Ce dont vous avez besoin

  • Un ordinateur sur lequel est installé Android Studio
  • Code de solution de l'application Cupcake.

Télécharger le code de démarrage pour cet atelier de programmation

Dans cet atelier de programmation, vous ajouterez des tests d'instrumentation à l'application Cupcake à partir du code de solution précédent.

Pour obtenir le code de cet atelier de programmation et l'ouvrir dans Android Studio, procédez comme suit :

Obtenir le code

  1. Cliquez sur l'URL indiquée. La page GitHub du projet s'ouvre dans un navigateur.
  2. Vérifiez que le nom de la branche correspond au nom spécifié dans l'atelier de programmation. Par exemple, dans la capture d'écran suivante, le nom de la branche est main.

fe29aa9112862a93.png

  1. Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.

5b0a76c50478a73f.png

  1. 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.
  2. Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
  3. 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

  1. Lancez Android Studio.
  2. Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open (Ouvrir).

a065e3d575fe607b.png

Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).

4f3b1e628c7695f1.png

  1. 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).
  2. Double-cliquez sur le dossier de ce projet.
  3. Attendez qu'Android Studio ouvre le projet.
  4. Cliquez sur le bouton Run (Exécuter) 11c34fc5e516fb1c.png 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 Cupcake se compose d'un écran d'accueil qui affiche un écran de commande avec trois options pour les quantités de cupcakes. Si vous cliquez sur une option, vous accédez à un écran sur lequel vous pouvez sélectionner un parfum, ainsi que la date de collecte de la commande. Vous pouvez ensuite envoyer votre commande à une autre application. Vous pouvez aussi l'annuler à tout moment.

3. Créer un répertoire de test unitaire

Créez un répertoire de test unitaire pour l'application Cupcake, comme vous l'avez fait dans les précédents ateliers de programmation.

4. Créer une classe de test unitaire

Créez une classe appelée ViewModelTests.kt.

5. Ajouter les dépendances requises

Ajoutez les dépendances suivantes au projet :

testImplementation 'junit:junit:4.+'
testImplementation 'androidx.arch.core:core-testing:2.1.0'

Maintenant, synchronisez votre projet.

6. Écrire un test ViewModel

Prenons un exemple simple. La première chose que nous faisons lorsque nous interagissons avec l'application sur un appareil ou dans un émulateur consiste à sélectionner une quantité de cupcakes. Nous allons donc commencer par tester la méthode setQuantity() dans OrderViewModel et vérifier la valeur de l'objet LiveData quantity.

La variable quantity, que nous allons tester, est une instance de LiveData. Le test des objets LiveData implique une étape supplémentaire, et c'est là que la dépendance que nous avons ajoutée entrera en jeu. Nous utiliserons LiveData pour mettre à jour l'UI dès qu'une valeur change. Notre interface utilisateur s'exécute sur ce que nous appelons le "thread principal". Si vous n'êtes pas encore familier avec ce concept et avec la simultanéité, ne vous inquiétez pas. Nous approfondirons ce sujet plus dans d'autres ateliers de programmation. Pour le moment, dans le contexte d'une application Android, considérez le thread principal comme le thread UI. C'est sur ce thread que s'exécute le code qui présente l'interface utilisateur à un utilisateur. Sauf indication contraire, un test unitaire suppose que tout s'exécute sur le thread principal. Toutefois, comme les objets LiveData ne peuvent pas accéder au thread principal, nous devons indiquer explicitement que les objets LiveData ne doivent pas appeler le thread principal.

  1. Pour indiquer que les objets LiveData ne doivent pas appeler le thread principal, nous devrons fournir une règle de test spécifique chaque fois que nous testons un objet LiveData.
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
  1. Nous pouvons maintenant créer une fonction appelée quantity_twelve_cupcakes(). Dans la méthode, créez une instance de OrderViewModel..
  2. Dans ce test, vous allez vérifier que l'objet quantity d'OrderViewModel est mis à jour lorsque la méthode setQuantity est appelée. Toutefois, avant d'appeler des méthodes ou d'utiliser des données dans OrderViewModel, il est important de noter que, lorsque vous testez les valeurs d'un objet LiveData, les objets doivent être observés pour que les modifications soient émises. Pour ce faire, vous pouvez utiliser la méthode observeForever. Appelez la méthode observeForever au niveau de l'objet quantity. Cette méthode nécessite une expression lambda, mais peut être laissée vide.
  3. Appelez ensuite la méthode setQuantity() en transmettant 12 comme paramètre.
val viewModel = OrderViewModel()
viewModel.quantity.observeForever {}
viewModel.setQuantity(12)
  1. Nous pouvons déduire avec quasi-certitude que la valeur de l'objet quantity est 12. Notez que les objets LiveData ne correspondent pas à la valeur elle-même. Les valeurs se trouvent dans une propriété appelée value. Effectuez l'assertion suivante :
assertEquals(12, viewModel.quantity.value)

Votre test devrait se présenter comme suit :

@Test
fun quantity_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.quantity.observeForever {}
   viewModel.setQuantity(12)
   assertEquals(12, viewModel.quantity.value)
}

Exécutez le test. Félicitations ! Vous venez d'écrire votre premier test unitaire LiveData. C'est une compétence essentielle dans le développement Android moderne. Comme il ne teste pas vraiment la logique métier, nous allons écrire un test un peu plus complexe.

L'une des principales fonctions d'OrderViewModel consiste à calculer le prix de notre commande. Ce calcul a lieu lorsque nous sélectionnons une quantité de cupcakes et une date de collecte. Étant donné que le calcul des prix s'effectue en mode privé, notre test ne peut pas appeler cette méthode directement. Seules les autres méthodes du modèle OrderViewModel peuvent l'appeler. Ces méthodes étant publiques, nous les appellerons pour déclencher le calcul du prix, afin de vérifier que la valeur du prix correspond à nos attentes.

Bonnes pratiques

Le prix est mis à jour lorsque la quantité de cupcakes est sélectionnée, ainsi que la date de collecte. Bien que ces deux méthodes doivent être testées, il est généralement préférable de ne tester qu'une seule fonctionnalité à la fois. Par conséquent, nous créerons des méthodes distinctes pour chaque test : une fonction permettant de tester le prix lorsque la quantité est mise à jour, et une autre fonction pour tester le prix lorsque la date de collecte est mise à jour. Personne ne souhaite voir un test échouer à cause d'un autre test qui a échoué.

  1. Créez une méthode appelée price_twelve_cupcakes() et annotez-la en tant que test.
  2. Dans la méthode, créez une instance d'OrderViewModel et appelez la méthode setQuantity(), en transmettant 12 en tant que paramètre.
val viewModel = OrderViewModel()
viewModel.setQuantity(12)
  1. En examinant l'élément PRICE_PER_CUPCAKE dans OrderViewModel, nous constatons que les cupcakes coûtent 2 $ chacun. Nous pouvons également voir que resetOrder() est appelé à chaque initialisation de ViewModel. Dans cette méthode, la date de collecte par défaut est la date du jour, et PRICE_FOR_SAME_DAY_PICKUP correspond à 3 $. Dès lors, le calcul est le suivant : 12 * 2 + 3 = 27. La valeur de la variable price, après avoir sélectionné 12 cupcakes, devrait être de 27 $. Confirmons que la valeur attendue de 27 $ est égale à la valeur de l'objet price LiveData.
assertEquals("$27.00", viewModel.price.value)

Exécutez maintenant le test.

Il devrait échouer.

17c8a24e4d7d635d.png

Le résultat du test indique que la valeur réelle est null. Il existe une explication à cela. Si vous examinez la variable price dans OrderViewModel, vous verrez ce qui suit :

val price: LiveData<String> = Transformations.map(_price) {
   // Format the price into the local currency and return this as LiveData<String>
   NumberFormat.getCurrencyInstance().format(it)
}

C'est un exemple de la raison pour laquelle vous devez observer LiveData lors des tests. La valeur de l'élément price est définie à l'aide d'une Transformation. Ce code convertit la valeur que nous attribuons à price en un format de devise pour que nous n'ayons pas à le faire manuellement. Cependant, ce code a d'autres implications. Lors de la transformation d'un objet LiveData, le code n'est pas appelé, sauf si c'est absolument nécessaire, ce qui permet d'économiser des ressources sur un appareil mobile. Le code n'est appelé que si nous observons l'objet à la recherche de modifications. Bien sûr, cela a lieu dans notre application, mais nous devons procéder de la même manière pour le test.

  1. Dans votre méthode de test, ajoutez la ligne suivante avant de définir la quantité :
viewModel.price.observeForever {}

Votre test devrait se présenter comme suit :

@Test
fun price_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.price.observeForever {}
   viewModel.setQuantity(12)
   assertEquals("$27.00", viewModel.price.value)
}

Si vous exécutez le test, il devrait réussir.

7. Code de solution

8. Félicitations

Voici les connaissances que vous avez acquises au cours de cet atelier de programmation :

  • Vous avez appris à configurer un test LiveData.
  • Vous avez appris à tester LiveData lui-même.
  • Vous avez appris à tester LiveData, qui a été transformé.
  • Vous avez appris à observer LiveData dans un test unitaire.