tests unitaires et doublures de tests : les simulacres ne sont pas des bouchons - club d'entraide des développeurs francophones
tests unitaires et doublures de tests : les simulacres ne sont pas des bouchons - club d'entraide des développeurs francophones
rechercher:
sur developpez.com
sur les forums
forums | tutoriels | f.a.q's | participez | hébergement | contacts
club
blogs
emploi
formation
dév. web
php
xml
python
autres
2d-3d-jeux
sécurité
systèmes
windows
linux
accueil
tv
conception
java
dotnet
visual basic
c
c++
delphi
pascal
ms-office
sql & sgbd
oracle
4d
forums conception
tutoriels conception
f.a.qs conception
uml
merise
livres conception
tests unitaires et doublures de tests : les simulacres ne sont pas des bouchonsdate de publication : 31/8/2007 , date de mise à jour : 29/10/2007
par
martin fowler (site web de martin fowler) (traduction par bruno orsier)
le terme "objet simulacre" est devenu populaire pour décrire des objets spéciaux qui imitent de vrais objets dans le but de les
tester. la plupart des environnements de développement ont maintenant des outils qui permettent de créer facilement des objets
simulacres. cependant, souvent on ne réalise pas que les objets simulacres sont un cas particulier d'objets de tests, qui
permettent un style de test différent. dans cet article j'explique comment les objets simulacres fonctionnent, comment ils
encouragent le test basé sur la vérification du comportement, et comment la communauté autour d'eux les utilise pour développer un
style de test différent.
dernière mise à jour significative : 02 janvier 2007
version pdf
(miroir)
version hors-ligne
(miroir)
notes du traducteur
remerciements
introduction
les tests traditionnels
les tests avec des objets simulacres
utilisation de easymock
la différence entre les simulacres et les bouchons
les styles de test classique et orienté-simulacre
comment choisir parmi les différences ?
le guidage du tdd
l'initialisation des classes de test
l'isolation des tests
le couplage des tests avec les implémentations
le style de conception
faut-il être un testeur classique ou orienté-simulacres ?
considérations finales
lectures complémentaires
notes du traducteur
le papier original en anglais est disponible ici.
les exemples de cet article sont en java, mais je les ai convertis en c#,
(en utilisant par exemple nmock2, typemock) afin de rendre cet article plus facilement accessible à la communauté .net.
le vocabulaire anglais employé dans la littérature de test est assez délicat à traduire en français (et je
suis d'ailleurs soulagé que martin fowler souligne que même en anglais ce vocabulaire est assez confus). j'ai
suivi les traductions proposées ici.
donc la traduction utilise la table de correspondance ci-dessous :
mot anglais
mot français
mock
simulacre
fake
substitut
stub
bouchon
dummy
fantôme
il y a plusieurs alternatives : objets fantaisie, mimes, etc.
le mot stub est particulièrement vide de sens pour un francophone... cette
discussion au sujet de la traduction de to stub out est assez révélatrice.
en ce qui concerne la traduction de l'acronyme tdd (pour test driven development), l'équivalent français ddt (développement dirigé par les tests) n'est pas très
parlant, ni très courant. par conséquent tdd n'a pas été traduit. de même pour bdd (behavior driven development) dont l'équivalent
français serait ddc (développement dirigé par le comportement). bdd n'a pas été traduit non plus.
remerciements
merci à miles, ricky81, uni[fr], ridekick pour leurs relectures de
cette traduction, et à alp pour sa relecture des exemples en c#.
merci également à ditch et giovanny temgoua
pour leurs diverses suggestions qui ont permis d'améliorer cet article.
introduction
j'ai rencontré pour la première fois le terme "objet simulacre" dans la communauté xp il y a quelques années. depuis je les ai
rencontrés de plus en plus. d'une part parce ce que beaucoup des principaux développeurs de ces objets ont fait partie de mes
collègues à thoughtworks à différents moments. d'autre part parce que je les vois de plus en plus dans la littérature sur les tests
influencée par xp.
mais trop souvent je vois que les objets simulacres sont mal décrits. en particulier je les vois souvent confondus avec les
bouchons - des utilitaires souvent employés dans les environnements de tests. je comprends cette confusion - je les ai considérés
comme similaires pendant un moment, mais des conversations avec les développeurs de simulacres ont fait régulièrement pénétrer un
peu de compréhension des simulacres dans mon crâne de tortue.
cette différence est en fait composée de deux différences distinctes. d'une part il y a une différence dans la vérification des
tests : il faut distinguer vérification d'état et vérification de comportement. d'autre part il y a une différence de philosophie
dans la manière dont le test et la conception interagissent, ce que je nomme ici par style "classique" et "orienté-simulacre"
du développement dirigé par les tests.
(dans la version précédente de cet essai, j'avais réalisé qu'il y avait une différence, mais je combinais les deux ensemble.
depuis, ma compréhension s'est améliorée et il est temps de mettre à jour cet essai. si vous n'avez pas lu le précédent essai vous
pouvez ignorer mes douleurs croissantes, car j'ai écrit cet essai comme si la précédente version n'existait pas. mais si vous êtes
familier avec la précédente version, vous pourriez trouver utile de noter que j'ai cassé la vieille dichotomie des tests basés sur
l'état / tests basés sur l'interaction en deux dichotomies : celle de la vérification état/comportement et celle du style
classique/orienté-simulacre. j'ai également ajusté mon vocabulaire pour qu'il corresponde à celui du livre de gerard meszaros xunit test patterns).
les tests traditionnels
je vais commencer par illustrer les deux styles avec un exemple simple
(l'exemple est en java, mais les principes sont valables pour n'importe quel
langage orienté-objet). nous voulons prendre un objet commande (order) et le
remplir à partir d'un objet entrepôt (warehouse). la commande est très
simple, avec un seul produit et une quantité. l'entrepôt contient les
inventaires de différents produits. quand nous demandons à une commande de se
remplir elle-même à partir d'un entrepôt, il y a deux réponses possibles.
s'il y a suffisamment de produit dans l'entrepôt pour satisfaire la commande,
la commande est considérée remplie, et dans l'entrepôt la quantité de produit
est réduite du montant approprié. s'il n'y a pas suffisamment de produit
alors la commande n'est pas remplie, et rien ne se produit au niveau de
l'entrepôt.
ces deux comportements impliquent quelques tests, qui sont des tests junit
assez conventionnels :
public class orderstatetester extends testcase {
private static string talisker = "talisker";
private static string highland_park = "highland park";
private warehouse warehouse = new warehouseimpl();
protected void setup() throws exception {
warehouse.add(talisker, 50);
warehouse.add(highland_park, 25);
}
public void testorderisfilledifenoughinwarehouse() {
order order = new order(talisker, 50);
order.fill(warehouse);
asserttrue(order.isfilled());
assertequals(0, warehouse.getinventory(talisker));
}
public void testorderdoesnotremoveifnotenough() {
order order = new order(talisker, 51);
order.fill(warehouse);
assertfalse(order.isfilled());
assertequals(50, warehouse.getinventory(talisker));
}
note du traducteur : cet exemple est disponible en c#
ici.
les tests xunit suivent une séquence typique de quatre phases :
initialisation, exécution, vérification, nettoyage. dans ce cas la phase
d'initialisation est faite partiellement dans la méthode setup (initialiser
l'entrepôt) et partiellement dans la méthode de test (initialisation de la
commande). l'appel à order.fill est la phase d'exécution. c'est là que
l'objet est incité à faire la chose que nous voulons tester. les
déclarations assert sont ensuite la phase de vérification, elles contrôlent
si la méthode exécutée a fait correctement son travail. dans ce cas il n'y
a pas de phase de nettoyage explicite, car le ramasse-miettes le fait
implicitement pour nous.
durant l'initialisation il y a deux sortes d'objets que nous mettons
ensemble. order est la classe que nous testons, mais pour que order.fill
fonctionne, nous avons également besoin d'une instance de warehouse. dans
cette situation order est l'objet sur lequel nous focalisons le test. les
gens orientés-tests aiment utiliser des termes comme l'"objet en cours de
test" ou le "système en cours de test" pour nommer une telle chose. chacun
de ces termes est difficile à dire, mais comme ils sont largement acceptés
je me force à les utiliser. suivant meszaros j'utilise système en cours de
test, ou encore l'abréviation sct.
donc pour ce test j'ai besoin du sct (order) et d'un collaborateur
(warehouse). j'ai besoin de warehouse pour deux raisons : d'une part pour
faire fonctionner le comportement testé (puisque order.fill appelle les
méthodes de warehouse) et d'autre part pour la vérification (puisqu'un des
résultats de order.fill est un changement potentiel dans l'état de
warehouse). au fur et au mesure que nous allons explorer ce sujet, vous
allez voir que nous insisterons beaucoup sur la distinction entre le sct et
les collaborateurs (dans la version plus ancienne de cet article je parlais
du sct comme l'"objet primaire" et des collaborateurs comme les "objets
secondaires").
ce type de test utilise la vérification de l'état, ce qui signifie que nous
déterminons si la méthode exécutée a fonctionné correctement en examinant
l'état du sct et de ses collaborateurs après l'exécution de la méthode.
comme nous le verrons, les objets simulacres permettent une approche
différente de la vérification.
les tests avec des objets simulacres
maintenant je prends exactement le même comportement mais j'utilise des
objets simulacres. pour ce code j'utilise la bibliothèque jmock pour définir
les simulacres. jmock est une bibliothèque java pour les objets simulacres.
il y a d'autres bibliothèques pour objets simulacres, mais celle-ci est à
jour et est développée par les créateurs de cette technique, donc c'est un
bon point de départ.
public class orderinteractiontester extends mockobjecttestcase {
private static string talisker = "talisker";
public void testfillingremovesinventoryifinstock() {
//setup - data
order order = new order(talisker, 50);
mock warehousemock = new mock(warehouse.class);
//setup - expectations
warehousemock.expects(once()).method("hasinventory")
.with(eq(talisker),eq(50))
.will(returnvalue(true));
warehousemock.expects(once()).method("remove")
.with(eq(talisker), eq(50))
.after("hasinventory");
//exercise
order.fill((warehouse) warehousemock.proxy());
//verify
warehousemock.verify();
asserttrue(order.isfilled());
}
public void testfillingdoesnotremoveifnotenoughinstock() {
order order = new order(talisker, 51);
mock warehouse = mock(warehouse.class);
warehouse.expects(once()).method("hasinventory")
.withanyarguments()
.will(returnvalue(false));
order.fill((warehouse) warehouse.proxy());
assertfalse(order.isfilled());
}
note du traducteur : cet exemple est disponible en c#
ici.
concentrez vous d'abord sur le test testfillingremovesinventoryifinstock , car j'ai pris quelques raccourcis avec l'autre test.
tout d'abord, la phase d'initialisation est très différente. pour
commencer, elle est divisée en deux parties : les données et les attentes.
la partie données initialise les objets qui nous intéressent, et en ce sens
elle est similaire à l'initialisation traditionnelle. la différence réside
dans les objets qui sont créés. le sct est le même - une commande.
cependant le collaborateur n'est plus un entrepôt, mais un simulacre
d'entrepôt - techniquement une instance de la classe mock.
la deuxième partie de l'initialisation crée des attentes sur l'objet
simulacre. les attentes indiquent quelles méthodes devraient être appelées
sur les simulacres quand le sct est exécuté.
une fois que les attentes sont en place, j'exécute le sct. après
l'exécution je fais alors la vérification, qui a deux aspects. j'utilise
des assertions sur le sct - à peu près comme avant. cependant je vérifie
également les simulacres - en contrôlant qu'ils ont bien été appelés selon
leurs attentes.
la différence clé ici est comment nous vérifions que la commande a fait ce
qu'elle devait dans son interaction avec l'entrepôt. avec la vérification
d'état, nous faisons cela avec des assertions sur l'état de l'entrepôt. les
simulacres utilisent la vérification du comportement, et nous vérifions
alors si la commande a fait les bons appels de méthodes sur l'entrepôt.
nous faisons cela pendant l'initialisation en disant au simulacre ce qu'il
doit attendre, puis en demandant au simulacre de se vérifier lui-même
durant la phase de vérification. seule la commande est vérifiée à l'aide
d'assertions, et si la méthode testée ne change pas l'état de la commande,
alors il n'y a même pas d'assertions du tout.
dans le deuxième test je fais plusieurs choses différemment. premièrement
je crée le simulacre d'une autre manière, en utilisant la méthode mock dans
mockobjecttestcase au lieu du constructeur. c'est une méthode utilitaire de
la bibliothèque jmock, qui me permet d'éviter de faire explicitement la
vérification plus tard ; en effet tout simulacre crée avec cette utilitaire
est automatiquement vérifié à la fin du test. j'aurais pu faire cela avec
le premier test aussi, mais je voulais montrer la vérification d'une
manière plus explicite pour bien montrer comment fonctionne le test avec
des simulacres.
deuxièmement, dans le second test, j'ai relâché les contraintes sur
l'attente en utilisant withanyarguments. la raison est que le premier test
vérifie déjà que le nombre est bien passé à l'entrepôt, aussi le deuxième
test n'a pas besoin de répéter cet élément de test. si la logique de la
commande doit être modifiée plus tard, alors un seul test échouera, ce qui
facilitera l'effort de migration des tests. j'aurais également pu laisser
entièrement de côté withanyarguments, car c'est le fonctionnement par défaut.
utilisation de easymock
il y a un certain nombre de bibliothèques d'objets simulacres. je rencontre
assez fréquemment easymock, à la fois dans ses versions java et .net.
easymock permet également la vérification du comportement, mais a plusieurs
différences de style avec jmock qui méritent d'être discutées. voici à
nouveau nos tests :
public class ordereasytester extends testcase {
private static string talisker = "talisker";
private mockcontrol warehousecontrol;
private warehouse warehousemock;
public void setup() {
warehousecontrol = mockcontrol.createcontrol(warehouse.class);
warehousemock = (warehouse) warehousecontrol.getmock();
}
public void testfillingremovesinventoryifinstock() {
//setup - data
order order = new order(talisker, 50);
//setup - expectations
warehousemock.hasinventory(talisker, 50);
warehousecontrol.setreturnvalue(true);
warehousemock.remove(talisker, 50);
warehousecontrol.replay();
//exercise
order.fill(warehousemock);
//verify
warehousecontrol.verify();
asserttrue(order.isfilled());
}
public void testfillingdoesnotremoveifnotenoughinstock() {
order order = new order(talisker, 51);
warehousemock.hasinventory(talisker, 51);
warehousecontrol.setreturnvalue(false);
warehousecontrol.replay();
order.fill((warehouse) warehousemock);
assertfalse(order.isfilled());
warehousecontrol.verify();
}
}
note du traducteur : cet exemple est disponible en c#
ici.
easymock utilise la métaphore enregistrer/rejouer pour définir les attentes.
pour chaque objet collaborateur pour lequel vous désirez un simulacre, vous
créez un objet de contrôle et un simulacre. le simulacre implémente
l'interface de l'objet collaborateur, tandis que l'objet de contrôle vous
donne des fonctionnalités supplémentaires. pour indiquer une attente, vous
appelez la méthode sur le simulacre, avec les arguments que vous attendez.
vous faites ensuite un appel au contrôle si vous voulez une valeur de retour.
une fois que vous avez fini de définir les attentes, vous appelez replay sur
le contrôle - à ce stade le simulacre termine l'enregistrement et est prêt à
répondre au sct. une fois que c'est fait vous appelez verify sur le contrôle.
il semble que les gens sont souvent troublés à la première vue de la
métaphore enregistrer/rejouer, mais qu'ils s'y habituent rapidement. elle a
un avantage par rapport aux contraintes de jmock en ce que vous faites de
vrais appels de méthodes sur le simulacre au lieu de spécifier des noms de
méthodes dans des chaînes. ce qui veut dire que vous pouvez utiliser la
complétion de code de votre environnement de développement, et que tout
remaniement de noms de méthodes mettra automatiquement à jour les tests.
l'inconvénient est que vous ne pouvez pas relâcher les contraintes.
les développeurs de jmock travaillent sur une nouvelle version qui permettra
d'utiliser de vrais appels de méthodes.
la différence entre les simulacres et les bouchons
quand ils ont été introduits pour la première fois, beaucoup de gens ont
facilement confondu les objets simulacres avec la notion courante de test
avec des bouchons. depuis ils semblent que les différences sont mieux
comprises (et j'espère que la précédente version de cet article y a
contribué). cependant, pour comprendre complètement comment les simulacres
sont utilisés, il est important de comprendre à la fois les simulacres et
les autres types de doublures de tests ("doublures" ? ne vous inquiétez pas
si c'est un nouveau terme pour vous, attendez quelques paragraphes et tout
deviendra clair).
quand vous testez, vous vous focalisez sur un seul élément du logiciel à la
fois - d'où le terme courant de test unitaire. le problème est que pour
faire fonctionner un élément particulier, vous avez souvent besoin d'autres
éléments - par exemple dans notre exemple nous avons besoin de warehouse.
dans les deux types de tests que j'ai montré ci-dessus, le premier type
utilise un vrai objet warehouse, et le deuxième utilise un simulacre de
warehouse, lequel bien sûr n'est pas un vrai objet warehouse. utiliser un
simulacre est donc un moyen de ne pas utiliser un vrai warehouse dans le
test, mais d'autres formes de "faux" objets sont également utilisées.
le vocabulaire pour parler de ces notions devient vite confus - toutes
sortes de mots sont utilisés : bouchon, simulacre, substitut, fantôme. pour
cet article je vais suivre le vocabulaire du livre de gerard meszaros. il
n'est pas utilisé par tout le monde, mais je pense que c'est un bon
vocabulaire, et comme il s'agit de mon article j'ai le privilège de choisir
quels mots utiliser.
meszaros utilise le terme doublure de test comme terme générique pour tout
objet utilisé à la place d'un vrai objet dans le but de tester. ce terme
correspond au cascadeur qui double un acteur dans un film (un des buts de
meszaros était d'éviter tout nom déjà largement utilisé). meszaros définit
alors quatre types particuliers de doublures:
fantômes : des objets que l'on fait circuler, mais qui ne sont jamais réellement utilisés. habituellement ils servent juste à remplir des listes de paramètres.
substituts : des objets qui ont de véritables implémentations qui fonctionnent, mais qui généralement prennent des raccourcis qui les rendent impropre à l'utilisation en production (un bon exemple est une base de données en mémoire au lieu d'une vraie base de données).
bouchons : ces objets fournissent des réponses prédéfinies à des appels faits durant le test, mais généralement ne répondent à rien d'autre en dehors de ce qui leur a été programmé pour le test. les bouchons peuvent aussi enregistrer de l'information concernant les appels, par exemple un bouchon de passerelle de courriels peut mémoriser les messages qu'il a "envoyé", ou encore seulement le nombre de messages qu'il a "envoyés".
simulacres : les objets dont nous parlons ici: des objets préprogrammés avec des attentes, lesquelles constituent une spécification des appels qu'ils s'attendent à recevoir.
parmi toutes ces doublures, seuls les simulacres insistent sur la
vérification du comportement. les autres doublures peuvent (et généralement
le font) utiliser la vérification d'état. en fait les simulacres se
comportement vraiment comme les autres doublures dans la phase d'exécution,
car ils ont besoin de faire croire au sct qu'il parle à ses vrais
collaborateurs - mais les simulacres diffèrent dans les phases
d'initialisation et de vérification.
pour explorer un peu plus les doublures de test, nous devons étendre notre
exemple. beaucoup de gens utilisent une doublure uniquement si le vrai objet
est peu pratique à manipuler. ici, disons que nous voulons envoyer un
courriel si un order échoue. le problème est que nous ne pouvons pas envoyer
de vrais courriels à des clients pendant nos tests. donc à la place nous
créons une doublure de notre système de courriels, que nous pouvons contrôler
et manipuler.
nous pouvons alors commencer à voir la différence entre les simulacres et les
bouchons. pour tester ce comportement d'envoi de courriels, nous pourrions
écrire un simple bouchon comme ceci :
public interface mailservice {
public void send (message msg);
}
public class mailservicestub implements mailservice {
private list<message> messages = new arraylist<message>();
public void send (message msg) {
messages.add(msg);
}
public int numbersent() {
return messages.size();
}
}
nous pouvons alors utiliser la vérification d'état sur le bouchon comme suit :
class orderstatetester...
public void testordersendsmailifunfilled() {
order order = new order(talisker, 51);
mailservicestub mailer = new mailservicestub();
order.setmailer(mailer);
order.fill(warehouse);
assertequals(1, mailer.numbersent());
}
bien sûr c'est un test très simple - il vérifie seulement qu'un message a été envoyé.
nous n'avons pas testé qu'il a été envoyé à la bonne personne, ni qu'il avait le bon contenu, mais
il suffira à illustrer mon propos.
en utilisant des simulacres, ce test serait bien différent :
class orderinteractiontester...
public void testordersendsmailifunfilled() {
order order = new order(talisker, 51);
mock warehouse = mock(warehouse.class);
mock mailer = mock(mailservice.class);
order.setmailer((mailservice) mailer.proxy());
mailer.expects(once()).method("send");
warehouse.expects(once()).method("hasinventory")
.withanyarguments()
.will(returnvalue(false));
order.fill((warehouse) warehouse.proxy());
}
}
dans les deux cas j'utilise une doublure de test à la place du vrai service
de courriel. 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.
pour utiliser la vérification d'état sur le bouchon, j'ai besoin de quelques
méthodes supplémentaires sur le bouchon pour faciliter la vérification. par
conséquent le bouchon implémente mailservice mais ajoute des méthodes de test
supplémentaires.
les simulacres utilisent toujours la vérification du comportement, tandis
qu'un bouchon peut faire les deux. meszaros nomme les bouchons qui utilisent
la vérification de comportement comme des espions de test. la différence
réside dans la manière dont la doublure effectue exécution et vérification,
et je vous laisse le soin d'explorer cela vous-même.
les styles de test classique et orienté-simulacre
maintenant j'en suis au point où je peux explorer la deuxième dichotomie
entre le test classique et orienté-simulacre. le point principal est quand
utiliser un simulacre (ou une autre doublure).
le style classique de tdd consiste à utiliser de vrais objets si possibles,
et une doublure quand le vrai objet est trop compliqué à utiliser. ainsi un
adepte du tdd classique utiliserait le vrai warehouse et un double pour le
service de courriels. le type exact de doublure ne compte pas tant que cela.
un adepte du style orienté-simulacre, par contre, utilisera toujours un
simulacre pour tout objet ayant un comportement intéressant, et donc dans
ce cas pour la à la fois warehouse et le service de courriels.
bien que les différents outils de création de simulacres aient été conçus
avec le style orienté-simulacre en tête, beaucoup d'adeptes du style
classique les trouvent utiles pour créer des doubles.
une conséquence importante du style orienté-simulacre est le développement dirigé par le comportement (bdd). le
bdd a été initialement développé par mon collègue dan north comme une
technique pour aider les gens à mieux apprendre le développement dirigé par
les tests en insistant sur la technique de conception que représente le
tdd. cela a conduit à renommer les tests en comportements pour mieux
explorer comment le tdd aide à réfléchir à ce qu'un objet doit faire. le
bdd suit une approche orientée-simulacre, mais il va plus loin, à la fois
dans ses styles de nommage, et dans son désir d'intégrer la conception
dans sa technique. je ne vais pas plus loin ici, car le seul lien
avec cet article est que le bdd est une autre variation du tdd qui tend à
utiliser les simulacres. je vous laisse suivre le lien pour plus
d'informations.
comment choisir parmi les différences ?
dans cet article j'ai expliqué deux différences : vérification de l'état ou
du comportement, style classique ou orienté simulacre. quels sont les
arguments à garder l'esprit quand on fait un choix entre ces styles ? je
vais commencer par le choix entre la vérification de l'état ou du
comportement.
la première chose à considérer est le contexte. pensons-nous à une
collaboration facile, comme entre order et warehouse, ou une compliquée,
comme entre order et le service de courriels ?
si c'est une collaboration facile alors le choix est simple. si je suis un
adepte du style classique, je n'utilise pas de simulacre, bouchon ou autre
sorte de doublure. j'utilise le vrai objet et la vérification d'état. si je
suis un adepte du style orienté-simulacre, j'utilise un simulacre et la
vérification du comportement. pas de décision à prendre du tout.
si c'est une collaboration compliquée, il n'y a pas de décision si je suis
un adepte des simulacres - j'utilise juste des simulacres et la
vérification du comportement. si je suis un adepte du style classique,
alors j'ai le choix, mais il n'est pas bien important. habituellement les
adeptes du style classique décident au cas par cas, en utilisant le chemin
le plus rapide pour chaque situation.
donc comme nous le voyons, la décision entre vérification de l'état ou du
comportement n'est pas une grosse décision la plupart du temps. la vraie
difficulté est de choisir entre tdd classique et orienté-simulacre. il
apparaît que les caractéristiques de la vérification de l'état et du
comportement affectent cette discussion, et c'est là que je vais focaliser
mon énergie.
mais avant de le faire, je vais proposer un cas limite. de temps en temps
vous rencontrez des choses dont il est vraiment difficile de vérifier
l'état, même s'il ne s'agit pas de collaborations compliquées. un bon
exemple de cela est un cache. la particularité d'un cache est que vous ne
pouvez pas dire à partir de son état comment le cache a fonctionné - c'est
un cas où la vérification du comportement serait un choix sage même pour un
adepte pur et dur du style tdd classique. je suis sûr qu'il y a
d'autres exceptions dans les deux directions.
maintenant que nous approfondissons le choix entre styles
classique/orienté-simulacre, il y a des tas de facteurs à considérer, donc
je les ai organisés en groupes grossiers :
le guidage du tdd
les objets simulacres sont issus de la communauté xp, et l'une des
caractéristiques principales de xp est son insistance sur le développement
dirigé par les tests - dans lequel la conception d'un système évolue par
des itérations pilotées par l'écriture de tests.
ainsi il n'y a rien de surprenant à ce que les adeptes du style
orienté-simulacre parlent de l'effet sur la conception du test avec des
simulacres.
en particulier ils recommandent un style appelé développement dirigé par
les besoins. avec ce style vous commencez à développer une histoire
utilisateur en écrivant votre premier test pour l'extérieur de votre
système, en faisant d'un certain objet interface votre sct. en
réfléchissant aux attentes sur les collaborateurs, vous explorez les
interactions entre le sct et ses voisins - et donc vous concevez
effectivement l'interface externe du sct.
une fois que vous avez votre premier test qui tourne, les attentes sur les
simulacres fournissent les spécifications pour la prochaine étape, et un
point de départ pour les tests. vous transformez chaque attente en un test
sur un collaborateur et répétez le processus en progressant dans le système
un sct à la fois. ce style est également nommé "extérieur-vers-intérieur",
ce qui en est une très bonne description. il fonctionne bien avec les
systèmes organisés en couches. vous commencez d'abord en programmant
l'interface utilisateur en utilisant des simulacres des couches
sous-jacentes. ensuite vous écrivez vos tests pour la couche en-dessous, en
parcourant graduellement le système une couche à la fois. c'est une
approche très structurée et très contrôlée, dont beaucoup de gens pensent
qu'elle est utile pour guider les novices dans la programmation
orientée-objet et le tdd.
le tdd classique ne fournit pas tout à fait le même guidage. vous pouvez
faire une approche progressive similaire, en utilisant des méthodes
bouchons à la place de simulacres. pour faire cela, chaque fois que vous
avez besoin de quelque chose d'un collaborateur, vous codez simplement en
dur la réponse nécessaire pour faire marcher le sct. une fois que vous avez
la barre verte vous remplacez la réponse en dur par le code approprié.
mais le tdd classique peut faire d'autres choses également. un style
courant est "milieu-vers-extérieur". dans ce style vous prenez une
fonctionnalité et décidez ce dont vous avez besoin dans le domaine pour que
cette fonctionnalité marche. vous faites faire aux objets du domaine ce
dont vous avez besoin, et une fois qu'ils marchent vous établissez
l'interface utilisateur au-dessus. en faisant cela vous pouvez très bien ne
rien avoir à doubler du tout. beaucoup de gens aiment cette approche car
elle concentre l'attention sur le modèle du domaine d'abord, ce qui empêche
la logique du domaine de contaminer l'interface utilisateur.
je voudrais souligner que les adeptes des styles orienté-simulacre et
classique font cela une seule histoire utilisateur à la fois. il y a une
école de pensée qui construit les applications couche par couche, ne
commençant pas une nouvelle couche tant que celle en cours n'est pas
terminée. au contraire les adeptes du style classique et des simulacres
tendent à avoir une approche agile et préfèrent des itérations de petite
taille. par conséquent ils travaillent fonctionnalité par fonctionnalité
plutôt que couche par couche.
l'initialisation des classes de test
avec le tdd classique vous devez créer non pas seulement le sct mais aussi
tous les collaborateurs dont le sct à besoin pour répondre au test. bien
que l'exemple ci-dessus n'ait que peu d'objets, les tests réels impliquent
souvent un grand nombre de collaborateurs. habituellement ces objets sont
créés et supprimés lors de chaque exécution des tests.
les tests avec simulacres, cependant, ont seulement besoin de créer le sct
et des simulacres pour ses collaborateurs immédiats. ceci peut éviter un
peu du travail impliqué dans la construction de classes de test complexes
(au moins en théorie. j'ai entendu des histoires d'initialisations de
simulacres pas mal complexes, mais cela était peu être dû à une mauvaise
utilisation des outils).
en pratique les testeurs classiques tendent à réutiliser autant que
possible les initialisations de test complexes. la manière la plus simple
est de mettre le code d'initialisation dans une méthode setup xunit. des
initialisations plus compliquées peuvent avoir besoin d'être utilisées par
plusieurs classes de test, alors dans ce cas vous créez des classes
spéciales pour générer les initialisations. je les appelle habituellement
des mères d'objets, en me
basant sur une convention de nommage utilisée dans un projet xp précoce
chez thoughtworks. utiliser des mères est essentiel dans le test classique
de grande envergure, mais les mères représentent du code supplémentaire qui
a besoin d'être maintenu, et tout changement au niveau des mères peut avoir
des répercussions en chaîne à travers les tests. il peut aussi y avoir une
pénalité de performance à l'initialisation - bien que je n'ai pas entendu
dire que ce soit un problème sérieux si cela est fait correctement. la
création de la plupart des objets d'initialisation est bon marché, et quand
ce n'est pas le cas ils sont habituellement doublés.
en conséquence j'ai entendu chaque style accuser l'autre de représenter
trop de travail. les partisans des simulacres disent que créer les
initialisations est un gros effort, mais les classiques disent qu'elles
sont réutilisables alors qu'il faut créer des simulacres pour chaque test.
l'isolation des tests
si vous introduisez un bug dans un système avec du test basé sur des
simulacres, généralement il fera échouer uniquement les tests dont le sct
contient le bug. avec l'approche classique, cependant, n'importe quel test
d'objet client peut aussi échouer, ce qui conduit à des échecs aux endroits
où l'objet fautif est utilisé comme collaborateur dans le test d'un autre
objet. par conséquent, un échec dans un objet très utilisé cause une
cascade d'échecs de test à travers le système.
les testeurs utilisant des simulacres considèrent cela comme un problème
majeur; il conduit à beaucoup de débogage pour trouver la cause racine de
l'erreur et la corriger. cependant les classiques n'expriment pas cela
comme une source de problèmes. habituellement le coupable est assez facile
à identifier en examinant quels tests échouent et les développeurs peuvent
alors dire quels échecs dérivent de la cause racine. de plus si vous testez
régulièrement (comme vous le devriez) alors vous savez que l'échec a été
causé par ce que vous avez édité en dernier, donc il n'est pas difficile de
trouver l'erreur.
la granularité des tests peut être un facteur significatif ici. puisque les
tests classiques mettent en jeu de nombreux
objets réels, vous trouvez souvent un test particulier qui est le test
primaire pour un groupe d'objets, plutôt que pour un seul objet. si ce
groupe comprend beaucoup d'objets, alors trouver la vraie cause d'un bug
peut être beaucoup plus difficile. ce qui arrive ici c'est ce que les tests
ont une granularité trop grossière.
il est probable que les tests avec des simulacres sont moins enclins à
souffrir de ce problème, car la convention est de faire des simulacres pour
tous les objets au-delà de l'objet primaire, ce qui rend clair que des
tests de granularité plus fine sont nécessaires pour les collaborateurs.
ceci étant dit, il est aussi vrai qu'utiliser des tests à granularité trop
grossière n'est pas nécessairement un échec du test classique en tant que
technique, c'est plutôt dû à une mauvaise application du test classique.
une bonne règle empirique est de s'assurer d'isoler des tests de fine
granularité pour chaque classe. alors que des groupes sont parfois
raisonnables, ils devraient être limités à un tout petit nombre d'objets -
pas plus qu'une douzaine. de plus, si vous vous trouvez face à un problème
de débogage dû à des tests de granularité trop grossière, vous devriez
déboguer dans une optique tdd, en créant des tests de plus fine granularité
au fur et à mesure que vous avancez.
en l'essence les tests classiques xunit ne sont pas juste des tests
unitaires, mais aussi de mini tests d'intégration. par conséquent beaucoup
de gens aiment le fait que des tests sur des clients peuvent attraper des
erreurs que les tests principaux pour un objet auraient ratées, en sondant
particulièrement les zones où les classes interagissent. les tests avec les
simulacres perdent cette qualité. de plus vous courrez aussi le risque que
les attentes sur les simulacres soient incorrectes, ce qui résulte en des
tests unitaires qui passent au vert mais masquent des erreurs inhérentes.
a ce stade je dois souligner que quelque soit le style de test que vous
utilisez, vous devez le combiner avec des tests d'acceptance de granularité
plus grossière, tests qui opèrent à travers tout l'ensemble du système.
j'ai souvent croisé des projets qui étaient en retard dans l'utilisation de
tests d'acceptance, et qui l'ont regretté.
le couplage des tests avec les implémentations
quand vous écrivez un test avec simulacres, vous testez les appels du sct
vers l'extérieur pour vous assurer qu'il parle correctement à ses
fournisseurs. un test classique s'intéresse uniquement à l'état final - pas
à la manière dont cet état a été obtenu. les tests avec simulacres sont
ainsi plus fortement couplés à l'implémentation d'une méthode. changer la nature des
appels aux collaborateurs cassera généralement un test avec simulacre.
ce couplage entraîne plusieurs préoccupations. la plus importante est
l'effet
sur le tdd. dans le cas des tests avec simulacres, écrire le test vous fait
réfléchir à l'implémentation du comportement - et en effet les testeurs avec
simulacres voient cela comme un avantage. les classiques, cependant, pensent
qu'il est important de réfléchir seulement à ce qui arrive de l'interface
externe, et de laisser de côté toute considération d'implémentation jusqu'à
ce que vous ayez fini d'écrire le test.
le couplage avec l'implémentation interfère également avec le remaniement,
puisque les changements d'implémentation risquent beaucoup plus de casser
les tests que dans le cas du test classique.
cela peut être aggravé par la nature même des boites à outils de simulacres.
souvent les outils de simulacres spécifient des appels de méthodes et des
appariements de paramètres très spécifiques, même quand ils ne sont pas
pertinents pour le test en question. un des buts de la boite à outils jmock
est d'être plus flexible dans sa spécification des attentes, pour autoriser
le relâchement des attentes dans les zones où elles n'ont pas d'importance,
au prix de l'utilisation de chaînes qui rendent les remaniements plus
délicats.
le style de conception
pour moi, l'un des aspects les plus fascinants de ces styles de test est la
manière dont ils influencent les décisions de conception. comme j'ai parlé
avec les deux types de testeurs, je suis devenu conscient de quelques
différences entre les conceptions encouragées par les styles, mais je suis
certain que je ne fais qu'égratigner la surface de ce sujet.
j'ai déjà mentionné une différence dans la manière d'aborder les couches.
les tests avec simulacres supportent une approche
"extérieur-vers-intérieur" tandis que les développeurs qui préfèrent
travailler à partir du modèle du domaine tendent à préférer le test
classique.
a un niveau moins élevé, j'ai noté que les testeurs orientés-simulacres
tendent à s'éloigner des méthodes qui retournent des valeurs, pour
favoriser les méthodes qui agissent sur un objet collecteur. prenez
l'exemple du comportement consistant à rassembler de l'information d'un
groupe d'objets pour créer un rapport sous forme de chaîne de caractères.
une manière courante de procéder est d'avoir une méthode de rapport qui
appelle sur les différents objets des méthodes retournant des chaînes, et
qui assemble la chaîne résultat dans une variable temporaire. un testeur
orienté-simulacre passera probablement un tampon de caractères aux
différents objets et leur fera ajouter les différentes chaînes à ce tampon
- traitant ainsi le tampon comme un paramètre collecteur.
les testeurs avec simulacres parlent également plus d'éviter les désastres
en chaînes - les chaînes de méthodes du type
getthis().getthat().gettheother(). Éviter les chaînes de méthodes est
également connu sous le nom de la loi de demeter. alors que les chaînes de
méthodes sont une mauvaise odeur, le problème opposé des objets
intermédiaires boursouflés de méthodes de transfert est également une
mauvaise odeur. (j'ai toujours senti que je serais plus confortable avec la
loi de demeter si elle était appelée la suggestion de demeter).
l'une des choses les plus difficiles à comprendre dans la conception
orientée-objet est le principe "fais au lieu de demander"
qui vous encourage à dire
à un objet de faire quelque chose, au lieu de lui extraire des données pour
faire la chose en question dans du code client. les testeurs
orienté-simulacres disent que le test avec simulacres favorise ce principe
et évite les confettis de code "get..." qui se répand dans beaucoup trop de
code ces temps-ci. les classiques avancent qu'il y a beaucoup d'autres
manières de faire cela.
un problème reconnu avec la vérification d'état est qu'elle peut conduire à
la création de méthodes requêtes servant seulement à supporter la
vérification. il n'est jamais confortable d'ajouter des méthodes à l'api
d'un objet dans le seul but de tester; utiliser la vérification du
comportement évite ce problème. le contre-argument est que de telles
modifications sont souvent mineures en pratique.
les testeurs orientés-simulacres favorisent les interfaces de rôles
et assurent qu'utiliser ce
style de test encourage la création de plus d'interfaces de rôles, puisque
chaque collaboration a son propre simulacre et est donc plus sujette à
devenir une interface de rôle. ainsi dans mon exemple ci-dessus concernant
la génération d'un rapport en utilisant un tampon de caractères, un testeur
orienté-simulacre serait plus enclin à inventer un rôle particulier qui
aurait du sens dans ce domaine, et qui pourrait être implémenté par un
tampon de chaînes.
il est important de se rappeler que cette différence de style de conception
est un élément de motivation clé pour la plupart des testeurs
orientés-simulacres. les origines du tdd étaient un désir d'obtenir de
solides tests automatisés de régression qui supporteraient une conception
évolutionnaire. sur le chemin ses adeptes ont découvert qu'écrire des tests
d'abord représentait une amélioration significative du processus de
conception. les adeptes du style orienté-simulacre ont une forte idée de
quelle conception est une bonne conception, et ont développé des
bibliothèques de simulacres principalement pour aider les gens à développer
ce style de conception.
faut-il être un testeur classique ou orienté-simulacres ?
je trouve qu'il est difficile de répondre avec certitude. personnellement
j'ai toujours été un adepte du bon vieux tdd classique et jusque-là je ne
vois pas de raison de changer. je ne vois aucun bénéfice irréfutable pour
le tdd avec simulacres, et je suis préoccupé par les conséquences de
coupler les tests avec l'implémentation.
ceci me frappe particulièrement quand j'observe un programmeur
orienté-simulacre. j'aime vraiment le fait que pendant que vous écrivez le
test, vous vous concentrez sur le résultat du comportement, pas sur comment
il est fait. un adepte des simulacres réfléchit constamment à
l'implémentation du sct pour pouvoir écrire les attentes. cela me paraît
vraiment anti-naturel.
je souffre également de l'inconvénient de ne pas essayer le tdd avec
simulacres sur autre choses que des applications jouets. comme je l'ai
appris du tdd lui-même, il est souvent difficile de juger une technique
sans l'essayer sérieusement. je connais beaucoup de bons développeurs qui
sont des adeptes convaincus et très heureux des simulacres. donc bien que
je sois toujours un classique convaincu, j'ai préféré présenter les deux
arguments aussi équitablement que possible de telle sorte que vous puissiez
vous faire votre propre idée.
donc si le test avec simulacres vous paraît attractif, je vous suggère de
l'essayer. cela en vaut particulièrement la peine si vous avez des
problèmes dans certaines zones que le test avec simulacre est conçu pour
améliorer. je vois deux zones principales ici. la première si vous passez
beaucoup de temps à déboguer quand des tests échouent, parce qu'ils
n'échouent pas proprement et ne vous disent pas où est le problème. la
deuxième si vos objets ne contiennent pas suffisamment de comportements ; le
test avec simulacres peut encourager l'équipe de développement à créer des
objets plus riches en comportements.
considérations finales
au fur et à mesure que grandit l'intérêt pour les outils xunit et le
développement dirigé par les tests, de plus en plus de gens croisent les
simulacres. le plus souvent, ils apprennent une partie des outils pour
simulacres, sans comprendre complètement la division
classique/orienté-simulacre sur lesquels ils reposent. quelque soit votre
côté dans cette division, je pense qu'il est utile de comprendre cette
différence de vue. alors que vous n'avez pas besoin d'être orienté-simulacres
pour trouver pratiques les outils de simulacres, il est utile de comprendre
le raisonnement qui guide la plupart des décisions de conception du logiciel.
le but de cet article était, et est toujours, de souligner ces différences et
d'exposer les compromis entre elles. il y a plus dans la réflexion
orientée-simulacres que ce que j'ai eu le temps d'approfondir, en particulier
ses conséquences sur le style de conception. j'espère que dans les toutes
prochaines années nous verrons plus de choses écrites sur cela, et que cela
approfondira notre compréhension des conséquences fascinantes de l'écriture
de tests avant le code.
lectures complémentaires
pour une revue complète de la pratique du test unitaire avec xunit, gardez un
œil sur le livre gerard meszaros qui va sortir (avertissement : il est dans
mes séries). il maintient aussi un site web avec
les patrons du livre.
pour en savoir plus sur le tdd, il faut commencer par le livre de kent.
pour en savoir plus sur le style de test orienté-simulacre, le premier
endroit à visiter est le site mockobjects.com où steve freeman et nat
price recommandent le point de vue orienté-simulacre avec des papiers et un bon
blog. lisez en particulier l'excellent papier oopsla. pour plus
d'informations sur le développement dirigé par le comportement, une variante
du tdd très orientée-simulacre, commencez par l'introduction de dan north.
vous pouvez aussi en savoir plus sur ces techniques en allant voir les sites
web des outils jmock, nmock,
easymock, easymock.net (il y
a d'autres outils disponibles, ne considérez pas cette liste comme complète).
le papier original sur les objets simulacres
a été présenté à xp2000, mais il est un peu dépassé maintenant.
version pdf
(miroir)
version hors-ligne
(miroir)
copyright martin fowler, all rights reserved.
responsable bénévole de la rubrique conception : miles - contacter par email :
vos questions techniques : forum d'entraide conception - publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones nous contacter
- copyright © 2000-2007 www.developpez.com - legal informations.
Acceuil
suivante
tests unitaires et doublures de tests : les simulacres ne sont pas des bouchons - club d'entraide des développeurs francophones Les tests unitaires en JavaScript - JDN Développeurs Renaud Feil (HSC) : "Notre formation aux tests d'intrusion fournit ... Tests et diplômes -Ministère des Affaires étrangères- 8800 GT tests & infos TEST QI GRATUIT, tests de qi et tests en ligne gratuits Votre santé et vous - Tests médicaux à domicile NerdTests.com Fun Tests - Nerd Quiz Les tests d'instruments d'astronomie de Ciel & Espace - Ciel & Espace IQ test, beroepskeuzetest, beroepentest, online assessement testen ... Tests humour et Quizz en ligne Tests ADN: François Bayrou est prêt à signer avec la Gauche le ... Le Monde.fr : Des tests génétiques pour le regroupement familial Certif Express - Passage de tests blancs de Certification gratuitement Humour : 2 tests dvd Dvd - tous les derniers tests sur dvdrama.com Tests - Quotient intellectuel (QI) - Yahoo! Jeux Tests - Quotient sexuel (QX) - Yahoo! Jeux Tests : 3 lecteurs Creative, Zen, Zen Stone, Zen Vision W - Les ... Tests, Examens et Cours multimédias de code de la route en ligne ... Jeux video, soluces, trainers, programmes gratuits, tests ... Réseau Education Sans Frontières - The Guardian / English tests ... Tests, Formation continue, formation professionnelle et formation ... PC-TESTS :: Forum informatique - Accueil FAQ : Tests génétiques tests de personnalité gratuits, test gratuit et original de ... Les Tests - Tests Quizako.com création et publication gratuite de quiz, tests, et ... IQ Tests BBC - Surveys and Psychology Tests Immigration: Mariani répond aux critiques sur les tests ADN Rue89 Immigration : visa contre test ADN, la nouvelle règle française ... Tests unitaires - Club d'entraide des développeurs francophones Tests électroniques, interfaces de tests, test électronique pour ... Tests appareils photos numériques Zone Numérique Mac OS X Leopard : sortie, tests et mises à jour Thot / Tests de santé : cœur, énergie, activité physique, masse ... Yahoo! Jeux - Tests (QI, Q.I., Quotient intellectuel) Antivols vélos : conseils et tests de la FUBicy. References.be: Tests interactifs Tests Psychotechniques infirmiers tests psy test psycho IFSI JeuxActu.com - jeu video, actualité tests previews hardware ... Des chercheurs s’élèvent contre les tests ADN Tests cosmétiques, Botox, produits cosmétiques et d’entretien sans ... Tableaux des tests Design-up::Les tests unitaires Listes des tests Playstation 2 sur JeuxVideo.com Tests Produits Monster tests Testez vos connaissances QCM QI IQ Psycho et Tests Cours et tests de langue en France About unit tests - jd:/dev/blog ๑ Test QI - Test de personnalité Des vidéos, des tests et toute l'actualité Xbox 360 des sites ... Le Rucher.com Code de la route : le plus grand site du code de la route en ligne Tests TESTS PSYCHOTECHNIQUES : Plus de 50 tests en ligne. Tests online - ESL séjours linguistiques Tests matériels SVM