Le dummy, un indice pas si con à propos de votre design...
Je l’ai déjà évoqué dans le précédent article, je suis persuadé que nos tests nous fournissent des informations pertinentes quant au design de nos applications. Il faut savoir les entendre.
Pour une raison relativement évidente, je me suis retrouvé à expliquer les différences entre les différents types de doublures il y a peu. Cela m’a permis de me rendre compte que je n’utilisais que très rarement, pour ne pas dire jamais, de dummy, et ça m’a interrogé. Une question en entrainant une autre je me suis assez vite demandé dans quel cas un dummy est utile et ce que l’utilisation d’un dummy pouvait indiquer à propos du design du code.
Qu’est-ce qu’un dummy ?
Très rapidement, un dummy est un type de doublure qui sert à remplacer un collaborateur dont on ne va pas avoir besoin lors de l’exécution du test. On s’en sert souvent pour remplacer un collaborateur pénible à construire mais pourtant nécessaire à la construction du système sous test. Un dummy peut aussi servir en tant que paramètre d’une méthode. Dans les deux cas ce collaborateur ne sert pas et on fait généralement en sorte qu’un appel fait sur le dummy provoque une erreur.
Il existe plusieurs manières de créer un dummy, avec ou sans framework de mock, et les possibilités varient en fonction du langage que l’on utilise.
Pour en savoir plus sur les dummies, je vous invite à consulter la page de description de ce pattern sur le site de xUnit Patterns.
Une chose est sûre, les dummies nous parlent, et à nous d’essayer de les comprendre.
Un dummy comme argument pour la construction
Intéressons-nous au premier cas, celui où l’on utilise un dummy pour remplacer une dépendance injectée à la construction.
Pour avoir de quoi discuter prenons pour exemple une classe Cuisinier
qui nous permet d’allumer le gaz et de partir en pause.
class Cuisinier {
public function __construct(
private readonly Gazinière $gaziniere,
private readonly Briquet $briquet,
private readonly PaquetDeCigarettes $paquetDeCigarettes) {
}
public function allumerLeGaz() {
$this->gaziniere->ouvrirLeRobinet();
$feu = $this->briquet->allumerLeFeu();
$this->gaziniere->approcher($feu);
}
public function partirEnPause() {
$cigarette = $this->paquetDeCigarettes->sortirUneCigarette();
$feu = $this->briquet->allumerLeFeu();
$cigarette->allumerAvec($feu);
//...
}
}
Cette classe a 3 dépendances :
- une
Gazinière
, - un
Briquet
, - et un
PaquetDeCigarettes
.
Lorsque l’on va écrire des tests qui s’intéressent au premier comportement, l’allumage du gaz, seuls 2 collaborateurs vont nous êtres utiles, la Gazinière
et le Briquet
. Pour eux on va pouvoir utiliser de véritables collaborateurs ou une doublure autre qu’un dummy. Ici sans doute un mock. En revanche, le PaquetDeCigarette
ne nous intéresse absolument pas pour ce test et utiliser un dummy est une solution tout à fait acceptable.
Pour le second comportement, celui où notre cuisinier part en pause, c’est pour la Gazinière
que l’on peut utiliser un dummy. Pas besoin du fourneau pour s’en griller une.
Deux comportements, deux dummies.
Tu en as marre de tes tests ?
J’ai créé une formation vidéo qui aide les développeuses et développeurs à améliorer leurs tests automatisés.
Dans cette formation je partage les idées et techniques qui permettent de rendre des tests lents, qui cassent à chaque modification du code et sont incompréhensibles en des tests avec lesquels on a plaisir à travailler.
La présence de ces deux dummies devrait selon moi nous pousser à nous interroger sur le design de notre application :
- Est-ce que le design ne serait pas mieux en séparant les deux responsabilités de cette classe dans deux classes indépendantes : un cuisinier spécialisé dans l’allumage de gaz et un cuisinier dont l’unique utilité est de prendre des pauses ?
- Est-ce qu’avoir une classe
Cuisinier
pour faire l’orchestration de ces actions a vraiment du sens ? Ne devrait-on pas se débarrasser duCuisinier
et créer un concept de gazinière toujours prête à l’emploi, quitte à ce que celle-ci possède son propreBriquet
? Avoir un paquet de cigarettes qui fournit des cigarettes qui peuvent s’allumer sans besoin de taxer du feu ?
Il n’y a pas de réponse évidente à ces questions. Le code environnant, les préférences personnelles, les habitudes de l’équipe, et sans doute bien d’autres facteurs jouent sur les réponses que l’on peut apporter.
Oui mais le briquet !
De manière intéressante la métrique Lack Of Cohesion in Method version 4, ou LCOM4
de cette classe est de 1, ce qui semble indiquer un bon niveau de cohésion. C’est l’utilisation du Briquet
dans les deux méthodes qui permet à cette classe d’être perçue comme cohésive.
La présence de dummies nous offre ici un signal que la métrique n’est pas en mesure de donner et nous permet de poser de nouvelles questions :
- Est-ce que l’utilisation du
Briquet
dans les deux méthodes est une justification suffisante pour placer les deux comportements dans la même classe ? - Est-ce qu’on allume vraiment une cigarette comme on allume une gazinière ?
- Est-ce que ce
Briquet
n’est pas uniquement une possible implémentation deSystèmeDAllumageDeGazinière
? D’autres implémentations pouvant êtreAlumette
ouAllumagePiezoElectrique
. - Est-ce qu’on peut allumer une cigarette avec un
AllumagePiezoElectrique
? (non). - Est-ce qu’avoir deux systèmes d’allumage n’aurait pas plus de sens ? Un pour la cigarette et un pour la gazinière ? Si oui, le
LCOM4
de la classe passe à 2, signal qu’une séparation en 2 de la classe pourrait être intéressante.
Doit-on changer ce code ?
Oui, non, peut-être, je ne sais pas. À vous de voir, finalement.
Ce que je trouve intéressant, c’est que la présence d’un dummy nous offre un signal. Il nous pousse à nous interroger sur la répartition des responsabilités entre les différents collaborateurs :
- Doit-on splitter certaines classes en plusieurs ?
- Doit-on changer la manière dont les classes collaborent entre-elles ?
- Doit-on transférer certaines responsabilités d’une classe à une autre ? Ou dans un nouveau concept ?
Encore une fois, je ne pense pas qu’il y ait de réponse toute faite pour ces questions. La forme des tests, comme les résultats de métriques, n’est qu’un outil pour nous pousser à nous poser des questions mais c’est bien à un développeur ou une développeuse de faire l’effort de choisir ce qui lui semble le meilleur design au moment actuel.
Dans ma formation vidéo sur l'amélioration des tests automatisés nous explorons les différents types de doublures. Il vous est aussi possible de prendre rendez-vous et voir ensemble comment je peux vous apporter mon aide ou simplement pour discuter.