Tests unitaires de la calculette des timbres Marianne de Cap Rikiki

Le module « Marimod », développé par la société « Cap Rikiki », gère une grosse partie des opérations réalisables avec les timbres de la série « Marianne ». Il fournit en particulier une calculette permettant de déterminer les timbres à utiliser pour un montant donné.

En plus de livrer ce module à son client, Cap Rikiki livre des tests unitaires pour bien montrer que cela fonctionne. C’est d’autant plus simple pour Cap Rikiki que la méthode 3T (Tests en Trois Temps), inspirée des TDD (Test Driver Developement) a été utilisée sur le projet.

Le cœur du domaine de l’application est composé de plusieurs objets Java relatifs aux timbres. En particulier, il y a une interface utilisée par tous les modules :

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Timbre {

    final static int TYPE_EURO = 0;
    final static int TYPE_POIDS = 1;
   
    double getValeur();
    String getImage();
    String getSerie();
    int getHauteur();
    int getLargeur();
    boolean isAutocollant();
    int getType();
}

Quant au code de la calculette, il ressemble au suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MarianneCalculette {

    /**
     * Détermine les timbres à utiliser pour le montant indiqué,
     * en ordonnant les timbres par valeurs décroissantes.
     *
     * ex. 0,63 donne 1 rouge + 3 jaunes : 0,60 + 0,01 + 0,01 + 0,01
     * ex. 0,46 donne 4 gris + 1 bistre + 1 jaune : 0,10 + 0,10 + 0,10
     *     + 0,10 + 0,05 + 0,01
     *    
     * @param montant positif en euro
     * @return la suite de timbres
     * @throws IllegalArgumentException si le montant est négatif.
     */

    @Override
    public List<Timbre> calculerListe(double montant) {
        ...
        return uneListeOrdonneeDeTimbres
    }

L’objectif de la suite est d’écrire des tests pour bien tester ce bout de code.

Note : Dans la suite, je vous propose une des nombreuses solutions possibles.

Je commence par créer ma classe de test :

1
2
3
public class MarianneCalculetteTest {

}

Comme je sais que je vais avoir besoin d’un objet MarianneCalculette dans tous mes tests, je vais en créer une instance au niveau de ma classe directement.

1
2
3
4
5
6
    private static TimbreCalculette calc;

    @BeforeClass
    public static void doBeforeClass() {
        calc = new MarianneCalculette();
    }

Vous remarquez que je définit une variable de type de l’interface « TimbreCalculette » et que je lui affecte un objet du type « MarianneCalculette ». Je fais cette affectation dans une méthode annotée « @BeforeClass », qui va donc s’executer lors de la création de la classe de test.

J’aurais pu me contenter de l’annotation « @Before » mais l’affectation aurait alors été fait avant chacun de mes tests. Or je devine que l’instanciation de la classe « MarianneCalculette » provoque des chargements de tarifs en base. Cette opération pouvant être longue, je vais me l’éviter. En outre, je devine que l’appel à la méthode « calculerListe » est indépendante des appels précédents. Je n’ai donc pas à réinitialiser ma calculette à chaque utilisation.

Pour la suite, je vais simplement m’inspirer de ce qui est indiqué dans la partie Javadoc, ce qui sera déjà pas mal. Faute de mieux, je prend les éléments du Javadoc dans l’ordre.

Le premier test va donc ressembler au code suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    @Test
    public void testMontant063() {
        // Arrange
        final double montant = 0.63;
        final double[] resultatAttendu = { 0.60, 0.01, 0.01, 0.01 };

        // Act
        final List<Timbre> list = calc.calculerListe(montant);

        // Assert
        assertNotNull(list);
        assertEquals(resultatAttendu.length, list.size());
       
        for (int i = 0; i < resultatAttendu.length; i++) {
            assertEquals(resultatAttendu[i], list.get(i).getValeur());
        }

    }

Normalement, une méthode de test ne teste qu’une seule chose. Toutefois on peut ajouter des tests basiques. Ici je teste donc aussi la non nullité de la liste (c’est toujours mieux) et la taille…

Je vais aussi ajouter un peu de documentation à ce test.

1
2
3
4
5
6
7
8
9
    /**
     * Tests qu'on calcule la bonne liste de timbres, en fonction d'un montant.
     *
     * PARAM montant = 0.63
     * RESULT { 0.60, 0.01, 0.01, 0.01 }
     */

    @Test
    public void testMontant063() {
        ...

Dans le second test, je remarque que le principe ça être le même, au delta des valeurs. Je vais donc factoriser une partie.

Note : De manière générale, je ne cherche pas à factoriser inutilement tant qu’il n’y a qu’une seule utilisation. Par contre, je factorise dès la seconde utilisation du code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /**
     * Tests qu'on calcule la bonne liste de timbres, en fonction d'un montant.
     *
     * PARAM montant = 0.63
     * RESULT { 0.60, 0.01, 0.01, 0.01 }
     */

    @Test
    public void testMontant063() {
        // Arrange
        final double montant = 0.63;
        final double[] resultatAttendu = { 0.60, 0.01, 0.01, 0.01 };

        // Act - Assert
        doTestMontantXXX(montant, resultatAttendu);

    }
1
2
3
4
5
6
7
8
9
10
11
12
    private void doTestMontantXXX(final double montant, final double[] resultatAttendu) {
        // Act
        final List<Timbre> list = calc.calculerListe(montant);

        // Assert
        assertNotNull(list);
        assertEquals(resultatAttendu.length, list.size());

        for (int i = 0; i < resultatAttendu.length; i++) {
            assertEquals(resultatAttendu[i], list.get(i).getValeur());
        }
    }

Et bien entendu le second test :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    /**
     * Tests qu'on calcule la bonne liste de timbres, en fonction d'un montant.
     *
     * PARAM montant = 0.46
     * RESULT { 0.10, 0.10, 0.10, 0.10, 0.05, 0.01 }
     */

    @Test
    public void testMontant046() {
        // Arrange
        final double montant = 0.46;
        final double[] resultatAttendu = { 0.10, 0.10, 0.10, 0.10, 0.05, 0.01 };

        // Act - Assert
        doTestMontantXXX(montant, resultatAttendu);
    }

Je passe à la suite, toujours dans l’ordre du Javadoc, faute de mieux. Je vois que les montants passés doivent être positifs, sinon ça renvoie une exception : facile…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /**
     * Tests qu'on a une exception quand on passe une valeur négative.
     *
     * PARAM montant = -1.5 (valeur négative)
     * RESULT Exception
     */

    @Test(expected = IllegalArgumentException.class)
    public void testMontantNegatif() {
        // Arrange
        final double montant = -1.5;
        final double[] resultatAttendu = null;

        // Act - Assert
        doTestMontantXXX(montant, resultatAttendu);

    }

Bon, ça ne fait que trois tests mais c’est déjà un bon début.

J’avais pensé à un test pour vérifier que la liste de retour est bien triée par ordre décroissante, mais la comparaison avec une liste attendue fait déjà ce travail. De même que vérifier la somme des éléments de la liste est redondant… Des fois il suffit de faire simple…

Un cas qu’on aurait pu ajouter, c’est le montant max. En effet, on ne peut pas envoyer des courriers de plus de 3Kg. C’était indiqué dans le sujet de l’évaluation mais pas dans le Javadoc… Alors que faire ?…

Laisser un commentaire