Lorsqu’on réalise nos tests unitaires, le SUT (system under test) interagit avec différents objets dont le comportement n’est pas à tester.
On peut prendre l’exemple d’une méthode qui aurait pour but d’effectuer des calculs puis envoyer un mail via un service d’envoi: il n’y a pas de valeur ajoutée à initialiser ce service, on voudrait simplement vérifier que la méthode “sendMail” soit appelée.
On voudrait également ne pas avoir à initialiser des objets complexes lorsque ceux-ci ne sont pas nécessaires. C’est lorsqu’on cherche comment arriver à cette fin qu’on tombe sur des termes comme “Mock” (le plus connu) mais aussi Spy, Fake, Dummy etc… Tous ces termes désignent des objets destinés à remplacer les objets réels afin de réaliser des tests.
L’objectif de cet article est d’expliquer les différences entre ces différents termes pour pouvoir utiliser l’outil le plus adapté à vos besoins 🙂
Note: les exemples seront réalisés en Java avec le framework Mockito mais les concepts restent les mêmes indépendamment du langage/framework 🙂
I. Définitions
Dummy
Un Dummy est simplement une structure d’objet qui va permettre de satisfaire un constructeur.
Quand l’utiliser ?
Si je dois initialiser un objet qui prend différents objets dans son constructeur et que certains d’entre eux ne me seront d’aucune utilité pour les tests, je peux les remplacer par des dummies pour éviter une instanciation fastidieuse sans valeur ajoutée.
Exemple d’utilisation:
Admettons que je veuille tester une méthode qui ne manipule que les offers et le receipt de ma classe Store ci-dessous, je n’ai aucun intérêt à initialiser un SupermarketCatalog qui ne servira pas dans mes tests.
Cet objet permettra de remplir le contrat, c’est-à-dire qu’il fera office de SupermarketCatalog dans le constructeur mais il n’a pas d’implémentation.
Si on imagine que je possède des receipt et offers instanciés, je pourrais alors écrire:
Store myStore = new Store(receipt, offers, supermarketCatalogDummy)
Cela permet de ne pas avoir à initialiser d’objets complexes lorsqu’ils ne sont pas nécessaires à la portion que nous voulons tester !
Fake
Un ‘fake’ est un objet simplifié qui sera plus facile à utiliser pour les tests. On pourrait citer l’exemple le plus courant d’une implémentation simplifiée d’une persistance d’objets. Peut être que dans la réalité, vous persistez des informations sur AWS dans un S3 mais que pour vos tests, sauvegarder des informations dans un SQLite ou une base H2 serait suffisant: si vous créez une implémentation qui le permet: c’est un fake.
Exemple d’utilisation:
J’ai un catalogue qui sauvegarde des produits dans une base de données et j’en ai besoin pour mes tests. Je peux faire implémenter l’interface de ce catalogue à un fake et interagir avec une Hashmap plutôt qu’une base de données réelle.
package dojo.supermarket.model;
import java.util.HashMap;
import java.util.Map;
public class FakeCatalog implements SupermarketCatalog {
private Map<String, Product> products = new HashMap<>();
private Map<String, Double> prices = new HashMap<>();
@Override
public void addProduct(Product product, double price) {
this.products.put(product.getName(), product);
this.prices.put(product.getName(), price);
}
@Override
public double getUnitPrice(Product p) {
return this.prices.get(p.getName());
}
}
Stub
Les “stubs” se substituent aux appels de méthode afin de donner des réponses pré-faites à des appels
Exemple d’utilisation:
Lorsque la portion que vous voulez tester doit obtenir des informations provenant de l’extérieur (via un service, une base de données etc..), vous voulez que cette information soit toujours la même indépendamment de l’état réel du service extérieur (BDD down par exemple).
Note: les Spies sont des stubs qui peuvent enregistrer leur état mais nous ne le détaillerons pas dans cet article.
Mock
Les mocks se substituent aux appels de méthodes pour faire des vérifications: on peut par exemple vérifier qu’une méthode a été appelée sans pour autant l’exécuter.
Exemple de cas d’utilisation:
Lorsque votre méthode appelle une API externe qui va se charger de persister un objet par exemple, vous pouvez avoir envie de vérifier que cette méthode soit appelée.
verify(ProductAPIService).save('toothBruch')
II. Pourquoi ces définitions ne vous disaient peut-être rien?
Mon explication c’est que lorsqu’on utilise un framework de test, on ne parle que de Mock: Mockito, EasyMock, Powermock…
En plus ces objets “Mock” peuvent endosser le rôle de Dummy, Stub, Mock.. Donc Mock est devenu dans le langage courant, un terme qui définit toutes les doublures de tests.
D’ailleurs dans l’exemple sur les Dummy, pour le créer on a utilisé la ligne de code suivante :
Cet article n’est qu’une porte d’entrée vers les doublures de tests, il est important d’avoir une idée (même simplifiée) de ces différents termes pour aller plus loin dans l’étude de celles-ci et d’améliorer leur utilisation dans notre quotidien.
Maintenant que vous avez lu cet article, je vous recommande d’aller lire sans plus attendre notre article “Dans quelles conditions utiliser des Mocks dans ses tests?” et spoil, pas aussi souvent qu’on pourrait le penser! 😀
Si vous souhaitez aller plus loin, je vous conseille de jeter un œil aux sources de cet article.
Il arrive que la code review nous empêche de merge nos PR assez vite et qu’on se retrouve à tirer une branche d’une branche de travail pour avancer 😔
Une fois la première PR squash et merge, la PR issue de la seconde branche se retrouve avec des conflits 💥
Si vous avez déjà vécu cette situation, il y a de bonnes chances que vous ayez cherry-pick vos commits de travail sur une...
Il arrive souvent que pour tester unitairement des règles de validation, on doive tester le même cas mais avec des exemples différents. Sans tests paramétrés, ça revient à faire un test par cas ce qui peut alourdir notre fichier de tests.
On va voir comme...
Avant .Net 8, tester du code qui utilise DateTime.Now() n’était pas trivial, on devait faire en sorte de mocker la Clock dans nos tests. Depuis .Net 8, c’est beaucoup plus facile grâce à TimeProvider inclut par défaut !
En deux mots, TimeProvider est une...
La gestion des erreurs de son API est très importante pour que les consommateurs puissent avoir une description claire du problème mais c’est souvent fastidieux à maintenir.
On va voir comment avoir des statuts de réponse cohérent et des messages d’erre...