Quels sont les inconvénients de l'écriture de code avant d'écrire des tests unitaires?

33

J'ai toujours vu la recommandation selon laquelle nous devrions d'abord écrire des tests unitaires, puis commencer à écrire du code. Mais j'estime qu'il est beaucoup plus confortable de procéder dans l'autre sens - écrire du code puis des tests unitaires, car j'estime que nous avons beaucoup plus de clarté après avoir écrit le code lui-même. Si j'écris le code et ensuite les tests, il se peut que je doive changer un peu mon code pour le rendre testable, même si je me concentre beaucoup sur la création d'un design testable. Par contre, si j’écris les tests puis le code, ceux-ci changeront assez souvent au fur et à mesure que le code se modifie.

Comme je vois beaucoup de recommandations pour commencer à écrire des tests et ensuite passer au codage, quels sont les inconvénients si je le fais dans l'autre sens - écrire du code puis des tests unitaires?

k25
la source
7
+1 pour demander pourquoi une certaine pratique est une "meilleure pratique" avant de l'adopter
TaylorOtwell le

Réponses:

37

Le rouge est la réponse. Le rouge est ce que vous obtenez du cycle de refactorisation rouge-vert de TDD que vous ne pouvez obtenir, testez-le en dernier. Commencez par écrire un test qui échoue. Regardez-le échouer. C'est ton rouge, et c'est important. Il dit: J'ai cette exigence et je sais que mon code ne la satisfait pas. Ainsi, lorsque vous passez à l’étape 2 (verte), vous savez avec la même certitude que votre code satisfait à cette exigence. Vous savez que vous avez modifié votre base de code de manière à satisfaire à l'exigence.

Des exigences (tests) développées après le code, sur la base du code, vous privent de ce type de certitude, de confiance.

Carl Manaster
la source
+1 - excellent point et point pris! Merci pour votre avis!
k25
7
Tests! = Exigences. Les tests et le code doivent être dérivés des exigences.
Bart van Ingen Schenau
2
@Bart van Ingen Schenau: La force de TDD réside précisément dans le fait de tester les exigences ARE. De plus, ce sont des besoins exécutables.
mouviciel
1
@Bart: Les tests unitaires sont souvent trop détaillés pour les exigences des clients (de haut niveau), mais l'idée est définitivement valable, surtout si nous considérons également des tests de plus haut niveau tels que les tests d'acceptation automatisés qui, une fois écrits, devraient être des exigences définitives. C'est l'essence même du "test d'acceptation agile".
Martin Wickman le
3
TDD ne concerne pas les tests, mais les spécifications. Les tests construits dans une approche TDD constituent un moyen de communication entre développeur et client permettant de s’entendre sur le produit à fabriquer.
mouviciel
18

Si vous écrivez le code, puis les tests, il est trop facile de tomber dans le piège d'écrire les tests pour que le code soit réussi, plutôt que d'écrire les tests pour vous assurer que le code est conforme à la spécification.

Cela dit, ce n'est certainement pas la seule façon de faire les choses, et il n'y a pas de "meilleure" façon de développer des logiciels. Si vous mettez beaucoup de travail initial dans le développement de cas de test, vous ne saurez pas si votre architecture proposée présente des failles avant bien plus tard - alors que si vous développez le code en premier, vous les rencontrerez plus tôt et pourrez les redéfinir avec moins de fond. effort.

Anon
la source
Oui, vous avez raison sur le premier point, mais je m'assure toujours de ne pas le faire. Si le test échoue, je consulte toujours le code, vérifie sa validité, puis vérifie si mon test est correct, puis modifie celui qui est erroné. Merci pour votre avis, je garderai ceci dans mon esprit .. +1 de moi pour le 1er point et le dernier point ...
k25
2
Mais si le test réussit? Le test peut réussir car il n’exerce pas le code d’intérêt; cela ne peut pas vraiment arriver avec TDD, car le test est supposé échouer initialement, et si tel n'est pas le cas, passez à l'étape 2 tant que vous ne l'avez pas corrigé. Donc, il y a un mode d'échec dans test dernier qui n'existe pas dans test en premier.
Carl Manaster
@ Carl Manaster - Oui, vous avez un point valable. Après avoir écrit le code, je suis parfaitement au courant des exigences et mon cas de test unitaire aurait donc raison (idéalement). Si mon cas de test réussit, je dirais que le code est correct, si le test échoue, je suivrai ce que j'ai dit. Mais, je suis 100% d'accord pour dire que vous avez un argument valable.
k25
@ k25: Le fait est que si votre scénario de test réussit, vous ne savez toujours pas si le code est correct ou non. Le cas de test peut être faux.
Anon.
@Anon. - oui vous avez raison, je vais également tenir compte de ce cas.
k25
12

En fait, les gens qui ont du retard sur TDD, c'est une question de test, mais ils oublient les deux autres lettres de l'acronyme. Quelque chose qui peut être lu ici: TDD sans T ou TDD ne concerne pas les tests .

Le truc, c'est que j'ai appris une pléthore d'autres choses étroitement liées au TDD. Peu importe que vous testiez d'abord: Ce qui compte, c'est de penser à la conception de logiciels .

Pour pouvoir même écrire des tests unitaires "dans le bon sens", c'est-à-dire qu'ils soient isolés, rapides et automatisés, vous remarquerez qu'il est temps de repenser la manière dont votre code sera arrangé de manière à ce qu'il soit plus facile. tester.

Personnellement, j'ai appris moi-même les principes SOLID sans savoir qu'il y avait une telle chose écrite. En effet, l'écriture des tests unitaires m'a obligé à réécrire les classes afin qu'elles ne deviennent pas trop complexes à tester. Cela a conduit à des choses comme:

  • Je devais déplacer des fonctionnalités qui n'avaient aucun sens, ou résider dans des méthodes privées, dans des classes séparées pour pouvoir les tester séparément. (Principe de responsabilité unique).
  • Je devais éviter les grandes structures d'héritage et étendre les implémentations avec la composition (important dans le principe Open-Closed).
  • Je devais être intelligent en matière d'héritage, j'utilisais des classes abstraites chaque fois que je voyais un code commun pouvant être partagé et utilisé avec des méthodes de stub (principe de substitution de Liskov).
  • Je devais écrire des interfaces et des classes abstraites pour pouvoir tester les classes séparément. Ce qui vous amène par inadvertance à écrire des objets fictifs. (Principe de séparation des interfaces)
  • Comme j'ai écrit beaucoup d'interfaces et de classes abstraites, j'ai commencé à déclarer des variables et des paramètres pour utiliser le type commun (principe d'inversion de dépendance).

Même si je ne fais pas de test tout le temps, je respecte les bons principes et pratiques OO que vous commencez à suivre, juste pour rendre les tests un peu plus faciles. Maintenant, je ne suis pas en train d'écrire du code pour lui-même. J'ai écrit du code afin qu'il puisse être facilement testé ou plus important encore; facilement entretenu .

Spoike
la source
1
+1 pour SOLID vient naturellement à vous lorsque vous pensez à la conception de logiciels.
dimanche
+1 (en fait je voulais donner +10 mais je ne peux pas). Exactement mes pensées - votre liste de points était extrêmement bonne. C'est une des raisons pour lesquelles j'ai posé cette question. Je sentais que les cours étaient devenus beaucoup plus nombreux lorsque j'ai commencé à écrire les tests unitaires après avoir écrit le code. Mais je voulais voir les avantages / inconvénients des deux côtés, merci pour vos avis!
k25
10

Toutes les autres réponses sont bonnes, mais il y a un point qui n'a pas été abordé. Si vous écrivez le test en premier, il s'assure que les tests sont écrits. Il est tentant, une fois que vous avez écrit du code de travail, de sauter les tests et de simplement le vérifier via l'interface utilisateur. Si vous avez la discipline de toujours avoir un test qui échoue avant d'écrire du code, vous pouvez éviter ce piège.

RationalGeek
la source
4

Si vous écrivez vos tests en premier, cela vous donne une autre chance de penser à votre conception, avant que celle-ci ne soit "coulée dans le marbre".

Par exemple, vous pouvez penser que vous avez besoin d’une méthode prenant un certain ensemble de paramètres. Et si vous avez écrit le code en premier, vous l'écriviez de cette façon et ajustiez le test aux paramètres spécifiés. Mais si vous écrivez le test en premier, vous pourriez penser "attendez une minute, je ne voudrais pas utiliser ce paramètre dans le code principal, alors je devrais peut-être changer l'API".

Anon
la source
+1 pour le premier point. Mais si nous n’arrivons pas au niveau des paramètres, qu’en est-il si la conception était discutée avec d’autres et acceptée?
k25
@ k25 - si quelque chose est difficile à utiliser comme prévu, il faut plus de réflexion. Parfois - très rarement - c'est juste une tâche difficile. Mais le plus souvent, cela peut être réduit à des tâches plus simples. Je n'ai pas de lien, mais Gosling ou Goetz ont réalisé une interview sur la conception des API il y a quelques années… qui valait la peine d'être recherchée sur Google.
Anon
bien sûr, merci pour les pointeurs, je vais certainement les regarder ...
k25
2

Comme je vois beaucoup de recommandations pour commencer à écrire des tests et ensuite passer à la codification,

Il y a une vraie bonne raison à cela.

Si vous dites "fais ce qui te semble juste", les gens font les choses les plus stupides et les plus folles.

Si vous dites "écrivez d'abord les tests", les gens pourraient au moins essayer de faire ce qui est bien.

Quels sont les inconvénients si je le fais dans l'autre sens - écrire du code puis les tests unitaires?

Habituellement, un test moche et une conception qui doit être retravaillée pour pouvoir être testée.

Cependant, ce n'est qu'un "habituellement". Certaines personnes font évoluer les conceptions et les tests en parallèle. Certaines personnes mettent en place du code testable et écrivent des tests sans retouche.

La règle "Test First" est spécifiquement là pour enseigner et instruire des personnes qui n'ont aucune idée du tout.

De la même façon, on nous dit de toujours regarder dans les deux sens avant de traverser la rue. Cependant, nous ne le faisons pas réellement. Et ce n'est pas grave. Je vis dans un pays avec la conduite à droite et je n'ai qu'à regarder à gauche lorsque je commence à traverser.

Lorsque je visite un pays où la conduite est à gauche, regarder à gauche ne peut me faire tuer.

Les règles sont énoncées très fortement pour une raison.

Ce que vous faites est votre propre problème.

S.Lott
la source
2

le point d'écrire le test est d'abord, il vous fait penser à

  • comment tester le code
  • l'interface que le code doit présenter pour pouvoir être testé

si vous faites quelque chose de simple, peu importe celle que vous écrivez en premier (bien qu'il soit bon de cultiver l'habitude du test d'abord), car le test sera simple et l'interface évidente

mais TDD évolue dans les tests d'acceptation, pas seulement les tests unitaires, et l'interface devient alors non triviale.

Steven A. Lowe
la source
1

Tout d'abord, si vous n'écrivez pas d'abord vos tests, vous ne faites pas de développement piloté par les tests (TDD). Les avantages sont nombreux et souvent difficiles à croire jusqu'à ce que vous le pratiquiez plusieurs fois. Voici les avantages que j'ai obtenus avec TDD par rapport au développement traditionnel:

  1. Un filet de sécurité de tests - vous permet de faire de grands changements sans craindre de casser quelque chose sans le savoir
  2. Conception organique - la conception que je finis est généralement différente de celle que j'aurais faite à partir de zéro et elle a toujours été meilleure
  3. Productivité - viser de petits objectifs (réussir ce test) et le faire (tous les tests réussis) fonctionne vraiment bien pour moi et me motive. Ajoutez la paire et ma productivité atteint de nouveaux sommets.

Books: Beck, K. Le développement piloté par les tests, par exemple

Bon exemple: http://jamesshore.com/Blog/Lets-Play/

Mike Polen
la source
+1 - bons points (surtout le 1er) et merci pour les liens!
k25
0

Lorsque vous écrivez un test, comment savez-vous qu'il détectera une condition d'échec? La réponse est "testez le test". Pour ce faire, vous écrivez le test en premier, vous le voyez échouer et ne le voyez réussir que lorsque l'unité testée a été codée avec succès (cycle rouge / vert / refactorisation mentionné dans l'une des autres réponses).

Écrire le code en premier et ensuite le test laisse ouverte la question de savoir si le test montrerait un échec honnête.

N'oubliez pas que vos tests spécifient les spécifications. Si vous devez réviser vos tests au fur et à mesure que votre code "prend forme", cela suggère que vos spécifications changent. Cela peut ou peut ne pas être une bonne chose. Cela pourrait signifier que votre compréhension du problème n’était pas correcte au départ. D'un autre côté, cela peut vouloir dire que vous testez "comment" l'unité fait son travail plutôt que ce qu'elle est supposée accomplir.

Zenilogix
la source