Simulacres et bouchons

Nous allons voir sur cette page comment se sortir de certaines situations particulières où il n'est pas toujours possible de tester tous les composants comme on ne souhaiterait, en mettant en œuvre des astuces de type mock (simulacre) ou stub (bouchon).

Le problème

Il existe certaines situations dans lesquelles on doit tester un composant sans pouvoir utiliser les composants dont il dépend. C'est notamment le cas quand on dépend d'un composant qui n'est lui-même pas encore développé (ce qui est assez fréquent dans les projets menés selon des méthodes agiles).

Pour pouvoir tout de même mener à bien les tests, nous allons devoir simuler ces composants ou leur comportement, grâce à des ruses techniques…

Tu te mock ou tu bouchonnes ?

On utilise pour cela des simulacres (mock objects) ou des bouchons (stubs). En théorie, la différence entre un mock et un stub n'est pas facile à cerner. En pratique, c'est beaucoup plus simple : prenons un exemple !

UNE CLASSE MÉTIER, UN BOUCHON ET UN SIMULACRE…

Une classe métier, un bouchon et un simulacre…

La figure ci-dessus présente une classe métier Personne dont pourrait bien dépendre d'autres classes que nous souhaiterions tester. Par exemple, dans un système de gestion des ressources humaines, une classe Équipe agrègerait des instances de la classe Personne. Dans ce cas, comment procéder si l'on a besoin de tester la classe Équipe alors que la classe Personne n'est pas encore développée ?

La figure présente deux ruses techniques qu'il est possible de mettre en œuvre dans le code de test :

  • Un bouchon (stub), qui est en fait une fausse classe, que l'on crée de toutes pièces en implémentant la même interface que la « vraie » classe (en l’occurrence Personne), et qui est une version simplifiée de la classe à tester, programmée pour l'occasion. On utilisera des instances de cette classe en lieu et place des instance de la vraie classe Personne.
  • Un simulacre (mock), qui consiste en la redéfinition du comportement d'une classe, du genre « si quelqu'un demande à une instance de Personne d'exécuter sa méthode direBonjour(), alors elle n'aura qu'à afficher "Coucou !" plutôt que d'exécuter le code de la vraie méthode direBonjour() programmée dans la classe Personne ».

Implémentation sur un exemple : client de mail

Pour illustrer l'utilisation mocks, nous allons à nouveau nous appuyer sur un exemple : imaginions que nous développions un client de messagerie très simple en Java, et que nous ayons à tester un module permettant l'affichage des messages. Voici un exemple d'un tel affichage :

=====================
Vos nouveaux messages
=====================
Bonjour !, reçu le Wed Dec 07 00:00:00 CET 3910, de : bob@u-picardie.fr
Comment ça va ?
Un test, reçu le Thu Dec 08 00:00:00 CET 3910, de : test@u-picardie.fr
Bonjour le test !
=====================

Diagramme de classes simplifié

VUE D'ENSEMBLE DES COMPOSANT DE NOTRE CLIENT DE MESSAGERIE

Vue d'ensemble des composant de notre client de messagerie

Réellement basique comme client de messagerie… mais cela suffira pour illustrer notre propos !

Présentation du code

La classe Message

Message.java

package com.parser;

import java.util.Date;

public class Message {

    Date date;
    String subject;
    String exp;
    String content;

    public Message(Date date, String subject, String exp, String content) {
        this.date = date;
        this.subject = subject;
        this.exp = exp;
        this.content = content;
    }

    public String toString() {
        return subject + ", reçu le " + date + ", de : " + exp + "\n" + content;        
    }
}

La classe Client (de messagerie)

Client.java

package com.parser;

import java.util.List;

public class Client {

    ServerStuff serverStuff;

    public Client(ServerStuff serverStuff) {
        this.serverStuff = serverStuff;
    }

    public String buildMessagesOutput() {
        List messages = this.serverStuff.getMessages();

        String output = "";

        output += "=====================\n";
        output += "Vos nouveaux messages\n";
        output += "=====================\n";
        for (Message message : messages) {
            output += message + "\n";
        }
        output += "=====================";    

        return output;
    }
}

Mise en œuvre des tests

Manque de bol, la brique de connexion au serveur et de récupération des nouveaux messages (la classe ServerStuff) n'a pas encore été développée. Tout ce que nous savons, c'est que d'après le design de l'application, ce composant est censé permettre la récupération d'une liste de messages List<Message>.

Pour tester notre module d'affichage, nous allons donc mettre en place un mock de la classe ServerStuff.

L'exemple Java présenté ci-dessous se base sur le framework de test Java jUnit, complété de la librairie Mockito.

Client.java

package com.parser;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

import junit.framework.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;


public class ClientTest extends TestCase {

    private Client client;

    public void setUp() {
        // La classe Client a besoin de ServerStuff
        // qui n'est pas encore développée...
        // Créons un mock :
        ServerStuff serverStuff = mock(ServerStuff.class);
        when(serverStuff.getMessages()).thenReturn((
            List)Arrays.asList(
                new Message(new Date(2010, 11, 7), "Bonjour !", "bob@u-picardie.fr", "Comment ça va ?"),
                new Message(new Date(2010, 11, 8), "Un test", "testeur@u-picardie.fr", "Bonjour le test !")
            )
        );
        // Nous pouvons à présent instancier le client :
        this.client = new Client(serverStuff);
    }

    public void testDisplayMessages() {        
        String output = this.client.buildMessagesOutput();
        assertTrue(output.equals("=====================\nVos nouveaux messages\n=====================\nBonjour !, reçu le Wed Dec 07 00:00:00 CET 3910, de : bob@u-picardie.fr\nComment ça va ?\nUn test, reçu le Thu Dec 08 00:00:00 CET 3910, de : testeur@u-picardie.fr\nBonjour le test !\n====================="));
    }
}

Comme vous pouvez le constater, la mise en œuvre d'un mock est réellement simple. Il s'agit simplement d'une déclaration du type :

Soit monMock un mock de la classe MaClasse.
Quand quelqu'un appelle la méthode maMéthode de monMock, alors il faudra retourner ceci : [ce que l'on veut retourner].