Test unitaire

CS-108 — Série 1

Introduction

Le but de cette série est de vous entraîner à écrire des tests unitaires, en testant une classe que nous vous fournissons et qui contient quelques erreurs.

La classe à tester — et corriger par la suite — met en œuvre ce qu'on appelle une file bornée d'entiers (bounded integer queue en anglais).

Tout comme un tableau dynamique, une file permet de stocker un nombre variable d'éléments d'un type donné, ici des entiers, mais avec une différence importante : les éléments d'une file sont toujours ajoutés à une de ses extrémités et retirés de l'autre. En cela, une file fonctionne comme une file de caisse de magasin, dans laquelle les nouveaux clients s'ajoutent à une extrémité et en ressortent de l'autre, d'où le nom.

De plus, une file est dite bornée lorsque le nombre d'éléments qu'elle peut contenir est limité. On appelle le nombre maximum d'éléments qu'une file bornée peut contenir sa capacité. Une file qui ne contient aucun élément est dite vide, tandis qu'une file qui en contient autant que sa capacité le permet est dite pleine.

Pour illustrer ces notions au moyen d'un exemple, imaginons que l'on crée une file bornée d'entiers d'une capacité de 5 éléments. Initialement, cette file est vide, ce qu'on peut représenter ainsi, les points d'interrogation désignant la capacité non utilisée de la file :

? ? ? ? ?

Ensuite, admettons que l'on ajoute l'entier 2024 à la file. Elle ressemble alors à ceci — si l'on fait l'hypothèse que les éléments sont ajoutés à droite et retirés de la gauche :

2024 ? ? ? ?

Ajoutons ensuite l'entier 12. On obtient alors la file suivante :

2024 12 ? ? ?

Finalement, ajoutons les entiers 1, 2 et 3, dans cet ordre. La file est maintenant pleine, et ressemble à ceci :

2024 12 1 2 3

Si on retire maintenant un élément de cette file, on retire celui qui se trouve à gauche. On obtient alors l'entier 2024, et la file n'est maintenant plus pleine, puisqu'on pourrait lui ajouter un nouvel entier.

12 1 2 3 ?

Si l'on ajoute maintenant un dernier entier, disons 50, la file ressemble à ceci :

12 1 2 3 50

Mise en place

Pour commencer, téléchargez l'archive Zip que nous mettons à votre disposition, extrayez son contenu puis ouvrez le dossier ainsi créé (TSTU) comme nouveau projet dans IntelliJ. Vous y trouverez les fichiers suivants :

BoundedIntQueue.java
qui contient l'interface du même nom, représentant une file d'entiers bornée,
queue.jar
qui contient la version compilée — c.-à-d. un fichier .class — d'une classe nommée BoundedIntQueueBuggy implémentant l'interface ci-dessus mais contenant quelques erreurs,
GetSecretURL.java
qui contient une classe qui ne sera utile qu'à l'exercice 3.

Une fois le contenu de l'archive importé dans votre projet, dans le panneau Project, faites un clic droit sur le dossier test et choisissez Mark Directory as puis Test Sources Root.

Ensuite, dans le menu File, choisissez l'entrée Project Structure…, puis cliquez sur Problems dans la partie gauche. Vous devriez voir une ligne mentionnant que la bibliothèque queue n'est pas utilisée :

1. Library queue is not used [Fix]

Cliquez sur [Fix], choisissez Add to Dependencies… puis OK.

Vous avez maintenant accès à la classe BoundedIntQueueBuggy qui se trouve dans le fichier queue.jar, et l'erreur qui était initialement signalée dans GetSecretURL disparaît donc.

Finalement, ajoutez la dernière version de JUnit à votre projet, en suivant les instructions de notre guide sur le sujet.

Exercice 1

Attention : pour faire cet exercice correctement, vous ne devez en aucun cas lire le code de la classe BoundedIntQueueBuggy. C'est la raison pour laquelle cette classe ne vous est pas fournie sous forme de code source (fichier .java), mais bien sous forme compilée (fichier .class, qui se trouve à l'intérieur de la bibliothèque queue.jar).

Malheureusement, IntelliJ contient ce que l'on nomme un « décompilateur » (decompiler), qui peut partiellement reconstruire un fichier Java à partir de sa version compilée. Donc si vous essayez d'accéder à la classe BoundedIntQueueBuggy, IntelliJ vous montrera sa version décompilée, et vous pourrez donc assez facilement voir les problèmes qu'elle contient.

Ce n'est bien entendu pas le but de cet exercice, qui est de vous entraîner à écrire des tests ! Pour le faire correctement, prenez donc soin d'écrire vos tests sans lire le code de BoundedIntQueueBuggy, même si vous en avez techniquement la possibilité.

Le but de ce premier exercice est d'identifier les problèmes de la classe BoundedIntQueueBuggy en écrivant des tests unitaires. Pour créer la classe de test, ouvrez le fichier BoundedIntQueue, puis dans le menu Navigate, choisissez l'entrée Test puis créez un nouveau test comme suggéré. Prenez garde à placer la nouvelle classe de test dans le paquetage cs108.

Une fois le squelette de test créé, vous devrez y ajouter des méthodes de test pour les différentes méthodes de la classe BoundedIntQueueBuggy. Notez que cette dernière possède un unique constructeur qui prend en argument la capacité de la file, sous la forme d'un entier. Ce constructeur devrait lever IllegalArgumentException si la capacité est inférieure à zéro.

Étant donné que BoundedIntQueueBuggy implémente l'interface BoundedIntQueue, lisez les commentaires JavaDoc attachés aux méthodes de cette dernière pour savoir quel est leur comportement attendu.

Pensez à écrire au minimum une méthode de test par méthode de l'interface, et à tester que les exceptions qui devraient être levées le sont bien. Ces tests devraient vous permettre de détecter un certain nombre de problèmes dans la classe fournie, dont vous devez prendre note avant de continuer.

Exercice 2

Écrivez maintenant une classe cs108.BoundedIntQueueOk, qui implémente l'interface BoundedIntQueue, mais de manière correcte. Il va de soi que vous pouvez utiliser le test écrit lors de l'exercice précédent pour la tester.

Pour mettre en œuvre la classe BoundedIntQueueOk, vous avez plusieurs options, plus ou moins simples. Nous vous en proposons trois de difficulté croissante, à vous de choisir celle qui vous convient le mieux :

  1. Vous pouvez utiliser un tableau dynamique d'entiers, de type ArrayList<Integer>, pour stocker les éléments de la file, en utilisant les méthodes add et remove pour ajouter et enlever les éléments.
  2. Vous pouvez utiliser un tableau d'entiers normal, de type int[], pour stocker les éléments de la file. La taille de ce tableau doit être égale à la capacité de la file, et l'élément en tête de file doit toujours se trouver à l'index 0, comme illustré plus haut. Avec une telle représentation, l'opération removeFirst est chère à mettre en œuvre puisqu'elle implique la copie de tous les éléments de la file afin de les décaler d'une position vers le bas. Notez que cette copie peut se faire au moyen d'un simple appel à la méthode arraycopy de la classe System.
  3. Vous pouvez utiliser une variante de la solution précédente, qui autorise l'élément en tête de file à se trouver n'importe où dans le tableau, et pas uniquement à l'index 0. Attention, cette variante est très efficace mais pas simple à mettre en œuvre !

Quelle que soit la solution que vous choisissez, pensez à placer des assertions dans votre code, aux endroits où cela vous semble judicieux.

Exercice 3

Une fois votre classe BoundedIntQueueOk écrite et correcte, exécutez le programme GetSecretURL fourni dans l'archive Zip téléchargée précédemment. Ce programme utilise une file bornée pour calculer et afficher une adresse Web. En l'exécutant sans le modifier, vous devriez voir le message suivant s'afficher :

Téléchargez le code source à cette adresse :
https://cs108.epfl.ch/+/pqg13l/BoundedIntQueueBuggy.java

Malheureusement, l'adresse fournie est invalide car elle a été calculée au moyen de la version incorrecte de la file bornée, BoundedIntQueueBuggy. Modifiez maintenant la méthode newBoundedIntQueue de la classe GetSecretURL pour qu'elle utilise votre mise en œuvre de la file bornée, puis relancez le programme. Si votre mise en œuvre de la file bornée est correcte, vous devriez voir une adresse différente, et en l'entrant dans votre navigateur, vous devriez pouvoir télécharger le code source de notre classe BoundedIntQueueBuggy.

Si tel est le cas, importez cette classe dans votre projet puis essayez de trouver et de corriger toutes les erreurs qu'elle contient. Pour la tester, utilisez bien entendu votre classe de test ! Notez que cette classe utilise la mise en œuvre rapide, mais plus compliquée, mentionnée à la fin de l'exercice 2.