Ceci est une ancienne révision du document !


TP Bibliothèque (tests unitaires, TDD, utilisation de Netbeans, et révisions de Java)

Ce TP est fortement inspiré de l'exercice de synthèse “série 10” proposé dans les cours de Java au S2, dans lequel il est question de gérer une petite bibliothèque de livres, et d'une liste de personnes. Cela dit, il contient quelques variations par rapport à l'énoncé initial. Par conséquent, vous ne devez pas considérer que c'est un corrigé de l'exercice, mais juste une “façon de faire”.

Les objectifs pédagogiques pour le cours de CVDA sont les suivants :

  • Apprendre à mieux maîtriser les différents aspects de Netbeans (ou de tout IDE équivalent).
  • Maîtriser les tests unitaires.
  • Appréhender le concept de programmation dirigée par les tests (TDD, Test Driven Development).
  • En bonus : utiliser Git.

Ce n'est pas obligatoire mais… ce serait une bonne idée que vous utilisiez un gestionnaire de versions tel que Git pour enregistrer les différentes étapes de votre projet… Rappelez-vous que c'est en vous entraînant que vous vous familiariserez le mieux avec ce type d'outils. Ce TP est l'occasion idéale de s'entraîner sans risque !

1. Créez un projet Java nommé TPBibliotheque.

2. Dans le package bibliotheque, créez une classe Personne.java selon les spécifications données ci-dessous. N'implémentez pas les méthodes. Contentez-vous de retourner des valeurs par défaut lorsque cela est nécessaire. Assurez-vous qu'aucune erreur n'est présente à la fin de la création de la classe (pas de symbole de Warning ou d'erreur dans la marge, ni auprès du nom du fichier dans l'explorateur de projet).

<note important>Attention : plus tard dans le TP, vous devrez générer la Javadoc. Pensez-y dès le début, sinon, ce sera très fastidieux.</note>

<note tip> Pendant que vous implémentez votre méthode, observez quelques outils que vous offre votre IDE (Netbeans ou autre) :

  • Auto-complétion : lorsque vous créez une méthode, l'accolade fermante s'insère automatiquement.
  • Aide à la génération de la Javadoc : lorsque vous tapez le début d'un commentaire de Javadoc, les paramètres sont générés automatiquement.
  • Suggestions d'implémentation : Netbeans propose de définir comme Final certaines variables. Pourquoi ?

</note>

3. Créez une classe de test pour la classe Personne.java. Vous pouvez choisir d'utiliser les fonctionnalités de génération automatique, ou bien le faire à la main, mais dans tous les cas, prenez bien garde à séparer le code de test du code métier. Le contenu de votre classe de test doit être le suivant. Notez que pour l'instant, on ne se préoccupe pas de la gestion du numéro de personne. Exécutez les tests et observer qu'aucun d'eux ne passe.

<note important>Si, au moment de l'exécution, votre projet contient des erreurs, c'est que vous avez manqué une étape. Corrigez les erreurs avant d'aller plus loin.</note>

PersonneTest.java
package bibliotheque;
 
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
 
/**
 *
 * @author Amélie Cordier   
 */
public class PersonneTest {
 
    public PersonneTest() {
    }
 
    @BeforeClass
    public static void setUpClass() {
    }
 
    @AfterClass
    public static void tearDownClass() {
    }
 
    @Before
    public void setUp() {
    }
 
    @After
    public void tearDown() {
    }
 
    /**
     * Test of getNumero method, of class Personne.
     */
    @Test
    public void testGetNumero() {
        fail("Test à implémenter plus tard");    
    }
 
    /**
     * Test of getNom method, of class Personne.
     */
    @Test
    public void testGetNom() {
        Personne alan = new Personne("Turing", "Alan", 1912);
        assertEquals("Turing", alan.getNom());
    }
 
    /**
     * Test of getPrenom method, of class Personne.
     */
    @Test
    public void testGetPrenom() {
        Personne alan = new Personne("Turing", "Alan", 1912);
        assertEquals("Alan", alan.getPrenom());
    }
 
    /**
     * Test of getAnNaissance method, of class Personne.
     */
    @Test
    public void testGetAnNaissance() {
        Personne alan = new Personne("Turing", "Alan", 1912);
        assertEquals(1912, alan.getAnNaissance());
    }
 
    /**
     * Test of getDernierNum method, of class Personne.
     */
    @Test
    public void testGetDernierNum() {
        fail("Test à implémenter plus tard");
    }
 
    /**
     * Test of setNumPers method, of class Personne.
     */
    @Test
    public void testSetNumPers() {
        Personne alan = new Personne("Turing", "Alan", 1912);
        alan.setNumPers(18);
        assertEquals(18, alan.getNumero());
    }
 
    /**
     * Test of setNomPers method, of class Personne.
     */
    @Test
    public void testSetNomPers() {
        Personne alan = new Personne("Turing", "Alan", 1912);
        alan.setNomPers("Minsky");
        assertEquals("Minsky", alan.getNom());
    }
 
    /**
     * Test of setPrenomPerso method, of class Personne.
     */
    @Test
    public void testSetPrenomPers() {
        Personne alan = new Personne("Turing", "Alan", 1912);
        alan.setPrenomPers("Marvin");
        assertEquals("Marvin", alan.getPrenom());
    }
 
    /**
     * Test of setAnNaissance method, of class Personne.
     */
    @Test
    public void testSetAnNaissance() {
        Personne alan = new Personne("Turing", "Alan", 1912);
        alan.setAnNaissance(1990);
        assertEquals(1990, alan.getAnNaissance());
    }
 
    /**
     * Test of toString method, of class Personne.
     */
    @Test
    public void testToString() {
        Personne alan = new Personne("Turing", "Alan", 1912);
        assertEquals("Turing, Alan, 1912", alan.toString());
    }
 
}

4. Implémentez les méthodes de la classe Personne de sorte à ce que tous les tests (sauf les deux tests relatifs à la gestion des numéros).

<note tip>Ajoutez l'annotation @Ignore pour ignorer les deux tests que l'on ne souhaite pas gérer tout de suite. Observez :

  1. Comment gérer l'import nécessaire ?
  2. Quel est l'impact de @Ignore sur le résultat de l'évaluation des tests ?

</note>

5. Nous allons maintenant traiter le cas du numéro de personne.

  • Quelles sont les différentes stratégies pour gérer le numéro de personne ? Quels sont les avantages et les inconvénients de chacune ?
  • Concevez et écrivez les tests permettant de vérifier le bon fonctionnement de l'affectation des numéros de personne.
  • Implémentez les méthodes correspondantes
  • Mettez à jour la méthode toString() de Personne pour afficher les n° de personne, et mettez également à jour le test correspondant.

<note warning>Attention : dans le corrigé proposé (code sur Github, à partir du commit 3071905) la solution retenue n'est pas conforme aux spécifications initiales du sujet ! La méthode getDernierNum() n'existe plus. De plus, la méthode setNumPers() est remplacée par un getNextNumPers() utilisé uniquement dans le constructeur de Personne. Notez enfin que cette dernière méthode est privée, de sorte à éviter les mauvais usages liés à la gestion des numéros de personnes. C'est un choix d'implémentation. D'autres solutions auraient été possibles. </note>

Partie 2. Gestion d'une liste de personnes

6. Créez une nouvelle classe ListePersonnes.java dont la mission sera de gérer une liste de Personne. Comme dans la question 2, n'implémentez pas les méthodes. Contentez-vous d'écrire un squelette fonctionnel. La classe doit contenir un constructeur, une méthode ajouter qui permet d'ajouter une personne à la liste, et une méthode appartient, qui permet de vérifier qu'une personne appartient bien à la liste.

<note tip> On peut légitimement se demander comment implémenter la méthode “appartient”. En effet, soit on considère que l'on doit vérifier si une Personne p appartient à la liste, ce qui est trivial à implémenter, mais pas forcément très utile dans la pratique, soit on considère que le problème est de savoir si une personne dont on connaît le nom et le prénom appartient à la liste… Ce deuxième cas est un peu plus difficile, mais c'est probablement ce qui va intéresser les utilisateurs de notre méthode ! Faites donc les deux !</note>

7. Cette fois-ci, c'est votre tour d'écrire les tests de cette classe. Générez la classe de test (manuellement, ou automatiquement, à votre convenance). Si vous la générez manuellement, observez qu'il est parfois nécessaire de modifier le code automatiquement généré ;) Une fois que vous avez fini d'écrire les tests, vérifiez qu'ils fonctionnent et qu'ils échouent (puisque le code métier n'est pas encore écrit).

8. Complétez maintenant votre classe ListePersonnes pour qu'elle passe les tests.

9. Il est temps de générer la javadoc pour vous assurer que le processus fonctionne correctement. Vous pourrez devrez toujours la mettre à jour plus tard. Que se passe-t-il pour les tests ? Est-ce qu'une javadoc est générée ?

Partie 3. Refactoring

10. Ne trouvez vous pas qu'il est fastidieux de recréer les personnes et les listes de personnes à chaque test ? Nous allons donc utiliser les outils proposés par JUnit (méthodes de setUp) pour remédier à cela. Refactorez tout le code que vous avez écrit jusqu'ici en utilisant les méthodes de setUp. Ensuite, exécutez à nouveau l'ensemble de vos tests pour vérifier que vous n'avez rien cassé.

<note tip>Le refactoring consiste à ré-organiser du code de façon à le rendre plus “propre”. C'est une étape normale du développement d'un programme : au fur et à mesure de l'avancement, nous avons les idées plus claires sur ce que nous voulons, et donc nos envies d'organisation évoluent. Il ne faut pas hésiter à refactorer souvent !. Si les tests unitaires sont bien faits, ils vous permettront de vérifier que le refactoring que vous avez fait ne “casse pas” votre code.</note>

<note tip>L'outil refactor de Netbeans permet de rechercher et remplacer le nom d'une variable / d'une méthode dans tout votre code. C'est un outil très utile dans le contexte du refactoring car il est plus performant qu'un simple “rechercher/remplacer” : il remplace toutes les occurrences de la variable ou de la méthode dans tout le code… et non l'occurrence de la chaîne de caractères. N'hésitez pas à tester cet outil pour mieux en comprendre le fonctionnement.</note>

Partie 4. Génération de code

11. Nous allons maintenant nous occuper de la classe Livre.java. Comme pour la classe Personne.java, c'est à vous d'écrire le squelette de la méthode selon les spécifications. N'avez-vous pas trouvé fastidieux le fait de devoir écrire la classe Personne.java la première fois ? N'auriez-vous pas préféré pouvoir générer le code automatiquement à partir du diagramme UML ? Et bien figurez-vous que c'est possible.

Vous trouverez ici un fichier que vous pouvez ouvrir avec ArgoUML après l'avoir extrait. Il contient le diagramme de classe contenant uniquement la classe Livre.

  • Ouvrez le fichier avec ArgoUML.
  • Explorez le projet pour afficher la classe Livre dans l'interface principale.
  • Si la classe apparaît comme vide, cliquez (bouton droit) dessus, et choisissez d'afficher tous les champs.
  • Cherchez comment générer du code Java directement à partir de ce diagramme de classes. Magique non ? Imaginez le temps que vous pourriez gagner sur un projet contenant plusieurs dizaines de classes et plusieurs packages !

12. Il est temps de finir la classe Livre. Récupérez la classe de test ci-dessous, complétez-là si vous le jugez utile, puis implémentez la classe livre de sorte à ce que tous les tests passent.

LivreTest.java
 

Partie 5. Finir le TP

<note important>En construction</note>

Partie 6. Finalisation des documents

  • Javadoc
  • Diagrammes UML