Comment sont vos bébés-pas en TDD?

37

Aujourd’hui, nous formions le TDD et avons constaté le malentendu suivant.

La tâche consiste pour l'entrée "1,2" à renvoyer la somme de nombres qui est 3. Ce que j'ai écrit (en C #) était:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]); //task said we have two numbers and input is correct

Mais d'autres gars ont préféré le faire autrement. Tout d'abord, pour l'entrée "1,2", ils ont ajouté le code suivant:

if (input == "1,2")
   return 3;

Ils ont ensuite introduit un test supplémentaire pour l'entrée "4,5" et ont modifié la mise en oeuvre:

if (input == "1,2")
   return 3;
else if (input == "4,5")
   return 9;

Et après cela, ils ont dit "D'accord, maintenant nous voyons le schéma" et ont mis en œuvre ce que j'avais initialement fait.

Je pense que la seconde approche correspond mieux à la définition de TDD mais ... devrions-nous être si strict à ce sujet? Pour moi, il est normal de sauter des pas de bébé triviaux et de les combiner en "jumelages" si je suis suffisamment sûr pour ne rien rater. Ai-je tort?

Mise à jour. J'ai commis une erreur en ne clarifiant pas que ce n'était pas le premier test. Il y avait déjà quelques tests, donc "return 3" n'était en réalité pas le code le plus simple pour satisfaire à l'exigence.

SibérienGuy
la source
25
Si petit que mes collègues jaillissent "Ooahhh dazso cuuuuuute"
Adel
6
@Adel: Presque étouffé dans mon petit déjeuner, clavier maintenant plein ou crachat et miettes
Binary Worrier
2
@Adel, comme pour le locuteur non natif, il est plutôt difficile pour moi de comprendre cet humour, mais je suppose que vos collègues aiment la question :)
SiberianGuy Le
8
@Idsa: Il transpose la réponse de ses collègues lorsqu'on lui montre les premiers pas de son enfant "Ooahhh dazso cuuuuuute" = "Oh, c'est trop mignon" Quand on voit les tests unitaires écrits par Adel, en regardant les tests unitaires, ils disent "Oh, c'est trop mignon". Réaction à un pas réel - bébés = réaction aux tests unitaires "petits pas".
Binary Worrier
3
@Binaryworrier souhaite que je pourrais vous donner de vrais points pour prendre le temps d'expliquer la parenté
Andrew T Finnell

Réponses:

31

Écrivez le code le plus simple qui fait passer les tests.

Pour autant que je sache, aucun de vous ne l'a fait.

Étape bébé 1.

Test: pour l’entrée "1,2", retourne la somme des nombres qui est 3

Faire échouer le test:

throw NotImplementedException();

Faites passer le test:

return 3;

Étape bébé 2.

Test: pour l’entrée "1,2", renvoyer la somme des nombres, qui est 3

Test: pour l’entrée "4,5", renvoyer la somme des nombres, qui est 9

Le deuxième test échoue, alors faites le passer:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]);

(Bien plus simple qu'une liste de if ... return)

Vous pouvez certainement faire valoir une mise en œuvre évidente dans ce cas, mais si vous parlez de le faire strictement par petites étapes, il s’agit des étapes correctes, IMO.

L'argument est que si vous n'écrivez pas le deuxième test, une étincelle brillante pourrait apparaître plus tard et "refactoriser" votre code pour qu'il se lise:

return input.Length; # Still satisfies the first test

Et, sans prendre les deux mesures, vous n’avez jamais fait passer le deuxième test au rouge (ce qui signifie que le test lui-même est suspect).

pdr
la source
En ce qui concerne votre saisie. Exemple de longueur, avec le même succès, je peux imaginer une implémentation folle qui ne sera pas détectée par les deux tests
SiberianGuy le
@ Idsa - Oui, absolument, et plus vous écrivez de tests, plus l'implémentation doit être folle. input.LengthN'est-ce pas exagéré, surtout si l'entrée de la méthode s'avère être une mesure d'un fichier quelque part et que vous avez imprudemment appelé votre méthode Size().
pdr
6
+1 En ce qui concerne la façon d'apprendre le TDD, c'est la bonne façon. Une fois que vous l’avez appris, vous pouvez parfois aller directement à la mise en œuvre évidente, mais pour avoir une idée du TDD, c’est beaucoup mieux.
Carl Manaster
1
J'ai une question concernant le "test" lui-même. Voulez-vous écrire un nouveau test pour l'entrée "4,5" ou modifier le test d'origine?
mxmissile
1
@mxmissile: j'écrirais un nouveau test. Cela ne prend pas beaucoup de temps et vous vous retrouvez avec deux fois plus de tests pour vous protéger lorsque vous effectuez une refactorisation ultérieure.
pdr
50

Je pense que la deuxième façon est stupide. Je vois l’intérêt de faire des pas assez petits, mais écrire ces petits pas en zygote (je ne peux même pas les appeler bébé) n’est qu’une perte de temps. Surtout si le problème initial que vous résolvez est déjà très petit.

Je sais que c'est de la formation et qu'il s'agit davantage de montrer le principe, mais je pense que de tels exemples font plus que mal le TDD. Si vous voulez montrer la valeur des petits pas, utilisez au moins un problème comportant une certaine valeur.

Christophe Vanfleteren
la source
+1 et merci de m'avoir permis de regarder en l'air et d'apprendre un nouveau mot (asinine)
Marjan Venema le
12
+1 pour avoir appelé cela stupide. TDD est tout beau et tel, mais comme avec toute technique de programmation à la mode moderne, vous devez veiller à ne pas vous perdre.
Départ le
2
"Surtout si le problème initial que vous résolvez est déjà très petit." - Si les entrées devaient être additionnées, je serais d'accord avec cela, mais je ne suis pas convaincu que "diviser une chaîne, analyser deux entières du résultat et les ajouter". La plupart des méthodes dans le monde réel ne sont pas beaucoup plus compliquées que cela. En fait, il devrait y avoir plus de tests à venir, pour couvrir des cas extrêmes tels que la recherche de deux virgules, des valeurs non entières, etc.
pdr
4
@pdr: Je suis d'accord avec vous pour dire qu'il devrait y avoir plus de tests pour gérer les cas extrêmes. Lorsque vous les écrivez et que vous remarquez que votre implémentation doit être modifiée pour pouvoir les gérer, faites-le absolument. J'imagine que le fait de prendre des mesures zygote vers le premier chemin heureux, la "mise en œuvre évidente", me pose problème au lieu de simplement l'écrire et de partir de là. Je ne vois pas l'intérêt d'écrire une déclaration si chaque fibre de mon corps sait qu'elle disparaîtra au prochain moment.
Christophe Vanfleteren le
1
@ChristopheVanfleteren: Lorsque Beck décrit une implémentation évidente, il utilise la somme de deux ints comme exemple et lance toujours un énorme avertissement dramatique sur la façon dont vous allez mourir de honte si votre paire / critique peut penser à un code plus simple qui rend le passer un test. C'est une certitude absolue si vous écrivez un seul test pour ce scénario. De plus, je peux penser à au moins trois manières "évidentes" de résoudre ce problème: diviser et ajouter, remplacer la virgule par + et évaluer ou utiliser regex. Le but de TDD est de vous conduire au bon choix.
pdr
19

Kent Beck en parle dans son livre Test Driven Development: By Example.

Votre exemple indique une " implémentation évidente " - vous souhaitez renvoyer la somme de deux valeurs d'entrée, ce qui est un algorithme relativement basique à réaliser. Votre contre-exemple tombe dans "simulez jusqu'à ce que vous le fabriquiez" (bien que ce soit un cas très simple).

Une implémentation évidente peut être beaucoup plus compliquée que cela - mais elle fonctionne lorsque la spécification d'une méthode est assez stricte - par exemple, renvoyer une version codée en URL d'une propriété de classe - vous n'avez pas besoin de perdre du temps avec beaucoup faux encodages.

Une routine de connexion à une base de données, en revanche, nécessiterait un peu plus de réflexion et de test afin d'éviter toute implémentation évidente (même si vous en avez peut-être déjà écrit plusieurs fois sur d'autres projets).

Du livre:

Lorsque j'utilise TDD dans la pratique, je bascule généralement entre ces deux modes de mise en œuvre. Lorsque tout se passe bien et que je sais quoi taper, je mets en Mise en œuvre évidente après mise en œuvre évidente (exécuter les tests à chaque fois pour faire en sorte que ce qui est évident pour moi est toujours évident pour l’ordinateur). Dès que j'obtiens une barre rouge inattendue, je recule, je passe à des implémentations factices et je refacture le bon code. Lorsque ma confiance est revenue, je reviens aux mises en œuvre évidentes.

HorusKol
la source
18

Je vois cela comme suit la lettre de la loi, mais pas son esprit.

Vos pas de bébé devraient être:

Aussi simple que possible, mais pas plus simple.

En outre, le verbe dans la méthode est sum

if (input == "1,2")
   return 3;

Ce n'est pas une somme, c'est un test pour des entrées spécifiques.

StuperUser
la source
4

Pour moi, il me semble bien de combiner plusieurs étapes de mise en œuvre triviales en une étape légèrement moins triviale - je le fais tout le temps aussi. Je ne pense pas qu'il faille être religieux pour suivre TDD chaque fois à la lettre.

OTOH cela ne s'applique que pour des étapes vraiment triviales comme l'exemple ci-dessus. Pour quelque chose de plus complexe, que je ne peux pas garder complètement dans mon esprit à la fois et / ou où je ne suis pas sûr à 110% du résultat, je préfère aller un pas à la fois.

Péter Török
la source
1

Comme le montre cette question, la taille des étapes peut être source de confusion. Une question que je me suis souvent posée lorsque j'ai commencé à écrire des applications pilotées par des tests était: Le test que j'écris contribue-t-il au développement de mes applications? Cela peut sembler trivial et sans rapport avec certains, mais accrochez-vous un instant avec moi.

Maintenant, quand je commence à écrire une application, je commence généralement par un test. Ce test est en grande partie lié à ma compréhension de ce que j'essaie de faire. Si je pense avoir le comportement d'un cours dans ma tête, le pas sera grand. Si le problème que j'essaie de résoudre est beaucoup moins clair, alors l'étape peut être simplement que je sais qu'une méthode appelée X est renvoyée et qu'elle renverra Y. À ce stade, la méthode n'aura même aucun paramètre et il est possible que le nom de la méthode et le type de retour changent. Dans les deux cas, les tests pilotent mon développement. Ils me disent des choses sur mon application:

Ce cours que j'ai dans la tête va-t-il réellement fonctionner?

ou

Comment vais-je pouvoir faire ce truc?

Le fait est que je peux basculer entre de grandes et de petites étapes en un clin d'œil. Par exemple, si un grand pas ne fonctionne pas et que je ne vois pas de solution évidente, je passerai à un plus petit pas. Si cela ne fonctionne pas, je vais passer à une étape encore plus petite. Ensuite, il existe d'autres techniques telles que la triangulation si je suis vraiment bloqué.

Si, comme moi, vous êtes un développeur et non un testeur, le but de TDD n'est pas d'écrire des tests, mais d'écrire du code. Ne vous attardez pas pour écrire plein de petits tests s’ils ne vous procurent aucun avantage.

J'espère que votre entrainement avec TDD vous a plu. IMHO si plus de personnes étaient testées infectées, le monde serait un meilleur endroit :)

Lexx
la source
1

Dans une introduction aux tests unitaires, j'ai lu la même approche (des étapes qui ont vraiment l'air minuscule), et en réponse à la question "à quel point devraient-elles être minuscules" quelque chose que j'ai aimé, qui était (paraphrasé) comme ceci:

C'est à peu près à quel point vous êtes sûr que les étapes fonctionnent. Vous pouvez faire de grands pas si vous voulez. Mais essayez-le simplement pendant un certain temps et vous découvrirez une confiance peu judicieuse dans les endroits où vous le prenez pour acquis. Ainsi, les tests vous aident à établir une confiance factuelle.

Alors, peut-être que votre collègue est un peu timide :)

keppla
la source
1

N’est-ce pas que la mise en œuvre de la méthode n’est pas pertinente tant que les tests aboutissent? L'extension des tests échouera plus rapidement dans le deuxième exemple, mais peut échouer dans les deux cas.

Thorsal
la source
1
Peu importe si vous ne voulez absolument pas perdre votre temps
SiberianGuy
1

Je suis d'accord avec les gens qui disent que ni la mise en œuvre la plus simple.

La méthodologie est si stricte qu'elle oblige à rédiger le plus de tests pertinents possible. Renvoyer une valeur constante pour un cas de test et l'appeler passe est acceptable, car cela vous oblige à revenir en arrière et à spécifier ce que vous voulez vraiment pour obtenir autre chose que des bêtises de votre programme. Utiliser un cas aussi trivial, c'est se tirer une balle dans le pied à certains égards, mais le principe est que des erreurs se glissent dans les lacunes de votre spécification lorsque vous essayez de «faire trop» et que vous réduisez l'exigence à la mise en œuvre la plus simple possible Le test doit être écrit pour chaque aspect unique du comportement que vous souhaitez réellement.

Tom W
la source
J'ai ajouté une mise à jour concernant "le retour d'une valeur constante"
SiberianGuy le