Bouchon (stub) ou Simulacre (mock) ?

Le test unitaire est un test qui provoque l’exécution d’une partie du code et qui l’analyse.  Il permet de garantir que le code exécuté fonctionne correctement. Il se doit donc d’être automatisé et répétable. Pour cela, cela nécessite fréquemment le besoin d’isoler le test unitaire des systèmes extérieurs (base de données, appels web-service distants).

Pour ce faire, il existe plusieurs méthodes, l’utilisation de bouchon (‘stub’) ou de simulacre (‘mock’). L’article de référence est un article de Martin Fowler « Les simulacres ne sont pas des bouchons« . Pour résumer, une méthode bouchonnée est appelée sur un objet bouchon réel, centré sur le système testé. Il ne peut jamais faire échouer le test. On regarde l’état de l’objet final à la fin du test et non les étapes qui ont permis d’obtenir cet état. C’est un test basé sur des états.

En ce qui concerne les simulacres, les assertions ne portent pas sur l’objet final mais sur la manière dont ce dernier a été obtenu. C’est un test basé sur le comportement de la méthode. On peut contrôler le nombre de fois qu’une méthode a été invoquée, vérifier que ses paramètres correspondent bien entre ce qui est défini et ce qui est exécuté et faire échouer le test si l’enchaînement de méthodes ne correspond pas à l’enchaînement attendu par exemple.

Easymock utilise dans son fonctionnement le plus basique des simulacres, qui permettent de construire un test de manière très simple : l’ordre des méthodes invoquées, le nombre d’appel à une méthode peut engendrer un échec du test. Potentiellement, les tests sont plus fragiles et plus difficile à maintenir.
Mais EasyMock permet également la création de bouchons qui peuvent être réutilisé. Il est ainsi possible de maintenir des bouchons partagés entre différents tests unitaires, ce qui permet une meilleure maintenabilité. Dans ce cas, nous ne nous intéressons pas au comportement, uniquement au résultat.

La création de bouchon à l’aide de andStubReturn()

public Service getServiceMock() {
   Service serviceMock =  createMock(Service.class);

   expect(serviceMock.appel1()).andStubReturn(Integer.valueOf(5));
   expect(serviceMock.appel2(anyObject())).andStubReturn(BigDecimal.TEN);

   replay(serviceMock);

}

La méthode andStubReturn signifie que cette méthode peut être appelée sans condition de nombre (0,1 …n) ni d’ordre. Il est simplement défini que si cette méthode est appelée, elle renverra un paramètre tel que défini via le andStubReturn. Il n’y a pas de verify(serviceMock) car les contrôles sur le comportement du mock ne nous intéressent pas.

La création de bouchon à l’aide des ‘niceMock’

Une autre possibilité lorsque l’on souhaite utiliser les bouchons (stub) est de créer un ‘nice mock’, qui est par défaut, renvoie 0, null ou false selon le paramètre de retour des méthodes :

myNiceMock  = createNiceMock(Service.class);

Toujours sans utiliser de verify.

Les avantages du stub sont nombreux : les tests sont plus faciles à maintenir, il est possible de réutiliser les stubs au sein de plusieurs classes de tests… Néanmoins,  il y a des situations où le test d’un comportement est plus adéquat que l’utilisation de bouchon comme par exemple, vérifier que certains paramètres sont bien calculés avant d’être envoyé à des services externes, ce qui est fréquemment mon cas.

6 Responses to “Bouchon (stub) ou Simulacre (mock) ?”

  1. Xav dit :

    Bonjour Mathilde,
    J’utilise beaucoup de mock dans mes tests unitaires, et je me sers énormément de Mockito. J’ai lu ton post avec intérêt car j’ai encore du mal à bien faire la distinction entre Bouchon (stub) ou Simulacre (mock) … Je suis surpris par la fin du post lorsque tu dis « Les avantages du stub sont nombreux » car dans les lignes qui précèdent il me semblait que tu parlais de « mock » … Aurais-je raté qq chose ? Merci

  2. Mathilde dit :

    Merci.
    Un nice mock peut être utilisé pour créer un bouchon (stub) dans la mesure où n’importe laquelle de ses méthodes peut être appelée n fois avec n paramètres différents, il ne retournera jamais d’erreur. On ne se préoccupe donc pas de l’enchainement ou de l’appel des méthodes quand on l’utilise de manière simple. J’ai édité pour que cela soit plus clair !

  3. Xav dit :

    OK, merci pour les précisions. Malheureusement, je reste un peu sur ma faim car je me vois toujours pas la différence entre mock et stub. Le comportement que tu décris est celui qu’on a en faisant un Mockito.mock(Service.class), rien à ajouter, les méthodes ont un fonctionnement par défaut.

    D’ailleurs, Mockito propose une méthode stub() pour une méthode, et recommande en fait l’utilisation de Mockito.when() (que je préfère et connais bien). Du coup, il n’est plus question que de mock, ça n’aide pas à y voir clair (en tout cas pour moi). Je vais creuser le sujet de mon côté … 😉

  4. Xav dit :

    Encore moi …

    Je suis allé reparcourir l’article dont tu donnes li lien, et la phrase suivante m’a éclaircit les idées :

    La différence est dans le fait que le bouchon
    utilise la vérification d’état alors que le
    simulacre utilise la vérification du
    comportement.

    Et effectivement, avec mes mocks de Mockito, je fais de la vérification de comportement.

  5. thierryler dit :

    A noter deux projets :

    * powermock (http://code.google.com/p/powermock/)
    * jmockit (http://code.google.com/p/jmockit)

    et une page de comparaison de framework de test
    http://code.google.com/p/jmockit/wiki/MockingToolkitComparisonMatrix

  6. grrr dit :

    « Bouchons ?! »
    « Simulacre ?! »

    Mort aux francisations foireuses!
    Nan mais de qui se moque t’on ?!! (sans jeu de mot bien sûr …)

Leave a Reply