Créer des tests unitaires

1. Avant de commencer

Dans les précédents ateliers de programmation, vous avez appris à créer un projet avec Android Studio, à modifier le code XML pour créer une interface utilisateur personnalisée pour votre application et à modifier la logique métier pour ajouter des fonctionnalités. Cet atelier de programmation met en avant l'importance des tests et se concentre sur les tests unitaires. Vous verrez ce à quoi ils ressemblent et découvrirez comment en créer.

Conditions préalables

  • Vous avez créé un projet dans Android Studio.
  • Vous avez de l'expérience en écriture de code dans Android Studio.

Points abordés

  • Pourquoi les tests sont-ils importants ?
  • À quoi ressemble un test unitaire ?
  • Comment écrire et exécuter un test unitaire ?

Ce dont vous avez besoin

2. Introduction

Maintenant que vous avez écrit du code Android, l'heure est venue de le tester. Pour commencer, vous vous familiariserez avec certaines notions spécifiques aux tests, puis examinerez de plus près les tests générés automatiquement dans un projet Android. Enfin, vous écrirez vos propres tests pour l'application Dice Roller. Cette leçon contient beaucoup d'informations, mais ne vous laissez pas intimider. N'essayez pas de sauter des étapes. Les tests prennent du temps et nécessitent beaucoup de pratique. Ne vous découragez pas si vous ne comprenez pas tout tout de suite.

Pourquoi les tests sont-ils importants ?

Au premier abord, vous pensez peut-être que votre application n'a pas besoin d'être testée. Lorsqu'une application est modeste et qu'elle offre des fonctionnalités limitées, il est facile de la tester manuellement et de déterminer si tout fonctionne comme prévu. Toutefois, lorsque l'application prend de l'ampleur, les tests manuels demandent beaucoup plus d'efforts que l'écriture de tests automatisés. De plus, lorsque vous commencez à travailler sur des applications de niveau professionnel et que la base d'utilisateurs est grande, les tests deviennent essentiels. Vous devez tenir compte de nombreux types d'appareils équipés de différentes versions d'Android. Arrivé à un certain stade, vous atteignez un niveau où les tests automatisés représentent la majorité des scénarios d'utilisation bien plus rapidement que les tests manuels. Lorsque vous exécutez des tests avant de publier le nouveau code, vous pouvez modifier le code existant afin d'éviter de publier une application présentant des comportements inattendus. N'oubliez pas que les tests automatisés sont exécutés à l'aide d'un logiciel, par opposition aux tests manuels effectués par une personne qui interagit directement avec un appareil. Les tests automatisés et manuels jouent un rôle essentiel pour que les utilisateurs bénéficient d'une expérience agréable. Toutefois, les tests automatisés peuvent être plus précis et optimiser la productivité de votre équipe, car personne n'est nécessaire pour les exécuter. De plus, leur exécution est beaucoup plus rapide que les tests manuels.

Gros plan sur les tests unitaires

Dans cet atelier de programmation, vous allez vous concentrer sur les tests unitaires. Vous couvrirez les tests d'instrumentation ultérieurement. Pour commencer, vous examinerez les tests générés lorsque vous créez une application Android via Android Studio. Vous vous entraînerez également à exécuter les tests tout en vous familiarisant avec l'écriture du code.

Dans un parcours précédent, vous avez vu où se trouvent les fichiers sources destinés aux tests. Les tests unitaires se trouvent toujours dans le répertoire test :

f02b380da4e8f661.png

  1. Ouvrez le fichier app/build.gradle et examinez les dépendances. Certaines dépendances sont accompagnées de la mention testImplementation et androidTestImplementation, qui désigne respectivement les tests unitaires et les tests d'instrumentation. À noter :

app/build.gradle

testImplementation 'junit:junit:4.12'

La bibliothèque JUnit alimente les tests unitaires et vous permet de marquer le code comme test afin de pouvoir le compiler et l'exécuter en vue de tester le code de l'application.

  1. Dans le répertoire test, ouvrez le fichier ExampleUnitTest.kt.

Vous devriez voir un exemple de test unitaire semblable à celui-ci :

ExampleUnitTest.kt

class ExampleUnitTest {
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
   }
}

Vous avez ajouté du code à l'application Dice Roller, mais vous n'avez probablement pas encore créé de test. C'est pourquoi il n'y a rien, à part le code générique créé automatiquement par Android Studio. Il s'agit d'un test arbitraire qui sert d'espace réservé pour des tests plus pertinents que le développeur écrira. Actuellement, ce bloc de code ne teste que 2 + 2 = 4. Cela va de soi. Examinons de plus près ce qui se passe :

  • Les fonctions de test doivent d'abord être annotées avec l'annotation @ Test importée depuis la bibliothèque org.junit.test. Les annotations sont semblables aux balises de métadonnées d'un extrait de code. Elles permettent de modifier la façon dont le code est compilé. Dans ce cas, l'annotation @Test indique au compilateur que la méthode suivante est un test et le laisse s'exécuter en tant que tel.

Après l'annotation, figure une déclaration de fonction, dans ce cas la fonction addition_isCorrect(). Au sein de celle-ci, la fonction assertEquals() suppose qu'une valeur attendue doit correspondre à une valeur réelle obtenue via la logique métier. Les méthodes d'assertion sont l'objectif final d'un test unitaire. En fin de compte, vous devez confirmer qu'un résultat obtenu à partir de votre code se trouve dans un état particulier. Si l'état du résultat correspond à l'état attendu, le test réussit. Si l'état du résultat ne correspond pas à l'état attendu, le test échoue. Dans ce cas, le code compare deux valeurs. La méthode assertEquals() utilise donc deux paramètres : une valeur attendue et une valeur réelle. Comme son nom l'indique, la valeur attendue correspond au résultat attendu, soit "4". La valeur réelle représente le résultat d'un extrait de code. En général, cela permet de tester un extrait de code provenant de l'application elle-même. Dans ce cas, il ne s'agit que d'un extrait de code arbitraire, par exemple 2 + 2. Sans plus tarder, exécutez ce test pour voir ce qui se passe.

Vous découvrirez ultérieurement de nombreuses façons d'exécuter des tests dans Android Studio. Pour l'instant, faites simple.

  1. À côté de la déclaration de la méthode addition_isCorrect, cliquez sur les flèches, puis sélectionnez Run 'ExampleUnitTest.addition_isCorrect'.

78c943e851a33644.png

C'est ce que l'on appelle un test positif. En d'autres termes, l'assertion est affirmative. 2 + 2 est égal à 4. Nous pouvons également écrire un test négatif avec une assertion qui utilise une négation. Exemple : 2 + 2 n'est pas égal à 5.

Dans le volet Exécuter, une capture d'écran semblable à celle-ci devrait s'afficher :

190df0c8ff787233.png

Plusieurs éléments indiquent que le test a réussi, à savoir les coches vertes et le nombre de tests ayant abouti.

aa7d361d8e4826ef.png

  1. Modifiez le test pour voir ce à quoi ressemble un échec. Remplacez 2 + 2 par 2 + 3, puis exécutez à nouveau le test. N'oubliez pas que vous n'utilisez que le code généré pour mieux comprendre le fonctionnement des tests. Ces modifications n'ont aucune incidence sur la fonctionnalité Dice Roller.

ExampleUnitTest.kt

class ExampleUnitTest {
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 3)
   }
}

Après avoir exécuté le reste, une capture d'écran semblable à celle-ci devrait s'afficher :

751ac8089cf4c47c.png

Le texte en rouge indique un échec du test. Dans le menu des résultats, cliquez sur les éléments du message d'erreur qui indique la raison de l'échec.

163708373e651ecc.png

Dans ce cas, le message indique que l'assertion a échoué, car le résultat attendu était 4 alors que la valeur réelle était 5. Rien de surprenant, sachant que vous avez remplacé la valeur réelle par 2 + 3, mais que vous avez laissé la valeur attendue 4. Vous pouvez également voir la ligne à laquelle le test a échoué. Dans ce cas, il s'agit de la ligne 15, représentée par ExampleUnitTest.kt:15.

  1. Par souci de clarté, remplacez la valeur attendue 4 par 5, puis exécutez à nouveau le test. Le test devrait maintenant réussir, car la valeur attendue correspond au résultat réel du code en question.

3. Écrire votre premier test unitaire

Maintenant que vous savez comment utiliser les tests unitaires, vous pouvez créer un test unitaire spécifique à l'application Dice Roller.

Comme vous avez pu le constater, la fonctionnalité principale de l'application Dice Roller repose sur un générateur de nombres aléatoires. Malheureusement, les générateurs de nombres aléatoires sont particulièrement difficiles à tester, car vous ne pouvez pas déterminer à l'avance quel nombre sera généré de manière aléatoire. L'objectif de ce test est de vous assurer que lorsque vous lancerez le dé ou que vous appellerez la méthode roll au niveau de la classe dice, vous recevrez un nombre approprié. Le test que vous écrirez vérifiera simplement que la sortie du générateur de nombres aléatoires est un nombre compris dans la plage que vous avez spécifiée pour le générateur.

  1. Dans le fichier ExampleUnitTest.kt, supprimez la méthode de test générée et importez les instructions. Votre fichier devrait à présent se présenter comme suit :

c06e8b402f293b5e.png

  1. Créez une fonction generates_number() :

ExampleUnitTest.kt

fun generates_number() {
}
  1. Annotez la méthode generates_number() avec l'annotation @Test. Notez que lorsque vous essayez d'appeler @Test, le texte est rouge. Cela est dû au fait que la méthode ne trouve pas la déclaration de cette annotation. Vous devez donc l'importer. Pour le faire automatiquement, appuyez sur Control+Enter (ou sur Options+Return si vous utilisez un Mac).

Si vous cliquez sur la ligne de code, vous devriez voir une invite d'importation de la déclaration :

bbe5791b9565588c.png

Vous pouvez également copier et coller le fichier import org.junit.Test après le nom du package, mais avant la déclaration de la classe. Le code devrait se présenter comme suit :

9a94c2bdf84adb61.png

  1. Créez une instance de l'objet Dice.

ExampleUnitTest.kt

@Test
fun generates_number() {
   val dice = Dice(6)
}
  1. Appelez ensuite la méthode roll() sur cette instance et stockez la valeur renvoyée.

ExampleUnitTest.kt

@Test
fun generates_number() {
   val dice = Dice(6)
   val rollResult = dice.roll()
}
  1. Pour finir, effectuez une assertion réelle. En d'autres termes, vous devez confirmer que la méthode a renvoyé une valeur comprise dans la plage que vous avez transmise (correspondant au nombre de faces du dé que vous avez défini). Dans ce cas, la valeur doit être supérieure à 0 et inférieure à 7. Pour ce faire, utilisez la méthode assertTrue(). Notez que lorsque vous essayez d'appeler la méthode assertTrue(), le texte est d'abord rouge. En effet, elle ne trouve pas la déclaration de cette méthode. Vous devez donc l'importer, comme pour l'annotation.

10eea07fc21bf998.png

Vous pouvez l'importer automatiquement, comme mentionné précédemment. Notez toutefois que vous disposez de plusieurs options cette fois-ci. Dans ce cas, il devrait s'agir de l'option issue du package org.junit.Assert :

5dbfba2ba0e37ac9.png

Vous pouvez également coller le code suivant après l'instruction d'importation pour l'annotation de test :

ExampleUnitTest.kt

import org.junit.Assert.assertTrue

Le code se présente désormais comme suit :

347f792f455ae6b5.png

Si vous placez le curseur entre les parenthèses et que vous appuyez sur Control+P (ou Command+P si vous utilisez un Mac), une info-bulle affiche les paramètres requis par la méthode :

865cf0ac47738e08.png

La méthode assertTrue() comporte deux paramètres : String et Boolean. Si l'assertion échoue, la chaîne correspond à un message qui s'affiche dans la console. La valeur booléenne est une instruction conditionnelle. Définissez le message de la manière suivante :

ExampleUnitTest.kt

"The value of rollResult was not between 1 and 6"

Comme indiqué précédemment, tester des nombres aléatoires est complexe, car il est impossible de prédire le nombre qui sera généré dans ce cas de figure. Le test doit donc porter sur une valeur qui se situe dans une plage précise. Définissez le paramètre de condition de la manière suivante :

ExampleUnitTest.kt

rollResult in 1..6

Le code devrait se présenter comme suit :

ExampleUnitTest.kt

@Test
fun generates_number() {
   val dice = Dice(6)
   val rollResult = dice.roll()
   assertTrue("The value of rollResult was not between 1 and 6", rollResult in 1..6)
}
  1. Cliquez sur les flèches situées à côté de la fonction, puis sélectionnez Run 'ExampleUnitTest.generates_number()'.

Si votre code ressemble à l'extrait de code précédent, le test devrait aboutir.

  1. Facultatif : pour vous entraîner davantage, modifiez le nombre de faces du dé (en définissant 4 ou 5 faces) sans changer l'assertion afin que le test échoue.

4. Félicitations

Les points suivants ne devraient maintenant plus avoir de secrets pour vous :

  • Importance des tests
  • Apparence d'un test unitaire
  • Exécution d'un test unitaire
  • Syntaxe de test courante
  • Écriture d'un test unitaire

En savoir plus