Sens des tests unitaires sans TDD

28

Nous avons un nouveau (assez gros) projet en cours de démarrage, que nous avions prévu de développer avec TDD.

L'idée de TDD a échoué (de nombreuses raisons commerciales et non commerciales), mais en ce moment, nous avons une conversation - devrions-nous quand même écrire des tests unitaires ou non. Mon ami dit qu'il n'y a aucun sens (ou presque zéro) à écrire des tests unitaires sans TDD, nous devrions nous concentrer uniquement sur les tests d'intégration. Je pense le contraire, qu'il est toujours utile d'écrire des tests unitaires simples, juste pour rendre le code plus à l'épreuve du temps. Qu'est-ce que tu penses?

Ajouté: Je pense que ce n'est pas un doublon de >> cette question << - Je comprends la différence entre UT et TDD. Ma question n'est pas sur les différences , mais sur le sens de l'écriture des tests unitaires sans TDD.

ex3v
la source
22
Je suis curieux de savoir quel raisonnement votre ami a pour une position aussi absurde ...
Telastyn
11
Je parierais que la grande majorité des projets avec des tests unitaires n'utilisent pas TDD.
Casey
2
Quels seront vos niveaux d'intégration? Quelles seront vos unités? À quelle fréquence refactorisez-vous sous chaque niveau de test? À quelle vitesse vos tests d'intégration seront-ils exécutés? Seront-ils faciles à écrire? Combien de cas combinatoires génère différentes parties de votre code? etc ... Si vous ne connaissez pas les réponses à ces questions, alors il est peut-être trop tôt pour prendre des décisions fermes. Parfois, TDD est génial. Parfois, les avantages sont moins clairs. Parfois, les tests unitaires sont essentiels. Parfois, un ensemble décent de tests d'intégration vous achète presque autant et sont beaucoup plus flexibles ... Gardez vos options ouvertes.
topo Reinstate Monica
2
Comme quelques conseils pratiques d'expérience, ne testez pas tout si vous ne faites pas TDD. Déterminez quels types de tests sont utiles. J'ai trouvé que les tests unitaires sur les méthodes d'entrée / sortie pures sont extrêmement précieux, tandis que les tests d'intégration à un niveau très élevé de l'application (par exemple, l'envoi de requêtes Web sur une application Web) sont également extrêmement précieux. Méfiez-vous des tests d'intégration de niveau intermédiaire et des tests unitaires qui nécessitent beaucoup de configuration fictive. Regardez également cette vidéo: youtube.com/watch?v=R9FOchgTtLM .
jpmc26
Votre mise à jour n'a aucun sens en ce qui concerne la question que vous avez posée. Si vous comprenez la différence entre TDD et tests unitaires, alors qu'est-ce qui vous empêche d'écrire des tests unitaires. Voter pour laisser votre question fermée bien que je puisse voir un argument pour la fermeture comme "pas clair sur ce que vous demandez" au lieu de dupliquer.

Réponses:

52

Le TDD est utilisé principalement (1) pour assurer la couverture, (2) et pour conduire une conception maintenable, compréhensible et testable. Si vous n'utilisez pas TDD, vous ne bénéficiez pas d'une couverture de code garantie. Mais cela ne signifie nullement que vous devez abandonner cet objectif et vivre allègrement avec une couverture de 0%.

Les tests de régression ont été inventés pour une raison. La raison en est qu'à long terme, ils vous font gagner plus de temps dans les erreurs évitées qu'ils ne nécessitent d'efforts supplémentaires pour écrire. Cela a été prouvé à maintes reprises. Par conséquent, à moins que vous ne soyez sérieusement convaincu que votre organisation est beaucoup, beaucoup mieux en génie logiciel que tous les gourous qui recommandent des tests de régression (ou si vous prévoyez de descendre très bientôt pour qu'il n'y ait pas de long terme pour vous), oui, vous devrait absolument avoir des tests unitaires, pour exactement la raison qui s'applique à pratiquement toutes les autres organisations dans le monde: car ils détectent les erreurs plus tôt que les tests d'intégration, et cela vous fera économiser de l'argent. Ne pas les écrire, c'est comme laisser passer de l'argent gratuit simplement traîner dans la rue.

Kilian Foth
la source
12
"Si vous n'utilisez pas TDD, vous ne bénéficiez pas d'une couverture de code garantie.": Je ne pense pas. Vous pouvez développer pendant deux jours et pendant les deux jours suivants, vous écrivez les tests. Le point important est que vous ne considérez pas une fonctionnalité terminée avant d'avoir la couverture de code souhaitée.
Giorgio
5
@DougM - Dans un monde idéal peut-être ...
Telastyn
7
Malheureusement, TDD va de pair avec la moquerie et jusqu'à ce que les gens cessent de le faire, tout cela prouve que votre test s'exécute plus rapidement . TDD est mort. Longue durée de test.
MickyD du
17
TDD ne garantit pas la couverture du code. C'est une hypothèse dangereuse. Vous pouvez coder contre des tests, réussir ces tests, mais vous avez toujours des cas limites.
Robert Harvey
4
@MickyDuncan Je ne suis pas sûr de bien comprendre votre préoccupation. La simulation est une technique parfaitement valable utilisée pour isoler un composant des autres afin que les tests du comportement de ce composant puissent être effectués indépendamment. Oui, poussé à l'extrême, il peut conduire à des logiciels sur-conçus, mais il en va de même pour toute technique de développement si elle est utilisée de manière inappropriée. En outre, comme le dit DHH dans l'article que vous citez, l'idée de n'utiliser que des tests système complets est tout aussi mauvaise, sinon pire. Il est important de faire preuve de jugement pour décider de la meilleure façon de tester une fonctionnalité particulière .
Jules
21

J'ai une anecdote pertinente sur quelque chose qui se passe en ce moment pour moi. Je suis sur un projet qui n'utilise pas TDD. Nos gens d'AQ nous font avancer dans cette direction, mais nous sommes une petite tenue et cela a été un long processus.

Quoi qu'il en soit , j'utilisais récemment une bibliothèque tierce pour effectuer une tâche spécifique. Il y avait un problème concernant l'utilisation de cette bibliothèque, il m'a donc été essentiellement demandé d'écrire une version de cette même bibliothèque par moi-même. Au total, cela a fini par représenter environ 5 000 lignes de code exécutable et environ 2 mois de mon temps. Je sais que les lignes de code sont une mauvaise métrique, mais pour cette réponse, je pense que c'est un indicateur de grandeur décent.

Il y avait une structure de données particulière dont j'avais besoin qui me permettrait de garder une trace d'un nombre arbitraire de bits. Étant donné que le projet est en Java, j'ai choisi Java BitSetet l'ai modifié un peu (j'avais également besoin de pouvoir suivre les 0s principaux , ce que BitSet de Java ne fait pas pour une raison quelconque .....). Après avoir atteint une couverture d'environ 93%, j'ai commencé à écrire des tests qui mettraient en fait à l'épreuve le système que j'avais écrit. J'avais besoin de comparer certains aspects de la fonctionnalité pour m'assurer qu'ils seraient assez rapides pour mes besoins finaux. Sans surprise, l'une des fonctions que j'avais outrepassées de l' BitSetinterface était absurdement lente lorsqu'il s'agissait de grands ensembles de bits (des centaines de millions de bits dans ce cas). D'autres fonctions remplacées reposaient sur cette seule fonction, il s'agissait donc d'un col de bouteille énorme .

Ce que j'ai fini par faire, c'était d'aller à la planche à dessin et de trouver un moyen de manipuler la structure sous-jacente de BitSet, qui est a long[]. J'ai conçu l'algorithme, j'ai demandé à mes collègues leur avis, puis j'ai commencé à écrire le code. Ensuite, j'ai exécuté les tests unitaires. Certains d'entre eux se sont cassés, et ceux qui l'ont fait m'ont indiqué exactement où je devais chercher dans mon algorithme afin de le réparer. Après avoir corrigé toutes les erreurs des tests unitaires, j'ai pu dire que la fonction fonctionne comme il se doit. À tout le moins, je pouvais être aussi convaincu que ce nouvel algorithme fonctionnait aussi bien que l'algorithme précédent.

Bien sûr, ce n'est pas à l'épreuve des balles. S'il y a un bogue dans mon code que les tests unitaires ne vérifient pas, je ne le saurai pas. Mais bien sûr, ce même bogue aurait pu également se trouver dans mon algorithme plus lent. Cependant , je peux dire avec une grande confiance que je n'ai pas à me soucier de la mauvaise sortie de cette fonction particulière. Les tests unitaires préexistants m'ont fait gagner des heures, voire des jours, à essayer de tester le nouvel algorithme pour s'assurer qu'il était correct.

C'est le point d'avoir des tests unitaires indépendamment de TDD - c'est-à-dire que les tests unitaires le feront pour vous dans TDD et en dehors de TDD tout de même, lorsque vous finirez par refactoriser / maintenir le code. Bien sûr, cela devrait être associé à des tests de régression réguliers, des tests de fumée, des tests flous, etc., mais les tests unitaires , comme son nom l'indique, testent les choses au niveau atomique le plus petit possible, ce qui vous indique où les erreurs sont apparues.

Dans mon cas, sans les tests unitaires existants, je devrais en quelque sorte trouver une méthode pour garantir que l'algorithme fonctionne tout le temps. En fin de compte, cela ressemble beaucoup à des tests unitaires , n'est-ce pas?

Shaz
la source
7

Vous pouvez diviser le code en gros en 4 catégories:

  1. Simple et change rarement.
  2. Changements simples et fréquents.
  3. Complexe et change rarement.
  4. Complexe et change fréquemment.

Les tests unitaires deviennent plus utiles (susceptibles d'attraper des bogues importants) plus vous descendez dans la liste. Dans mes projets personnels, je fais presque toujours du TDD sur la catégorie 4. Sur la catégorie 3, je fais habituellement du TDD à moins que les tests manuels soient plus simples et plus rapides. Par exemple, le code d'anticrénelage serait complexe à écrire, mais beaucoup plus facile à vérifier visuellement que l'écriture d'un test unitaire, donc le test unitaire ne vaut la peine que si ce code change fréquemment. Le reste de mon code que j'ai mis sous test unitaire après avoir trouvé un bug dans cette fonction.

Il est parfois difficile de savoir à l'avance dans quelle catégorie un certain bloc de code appartient. La valeur de TDD est que vous ne manquez accidentellement aucun des tests unitaires complexes. Le coût de TDD est tout le temps que vous passez à écrire les tests unitaires simples. Cependant, généralement, les personnes expérimentées dans un projet savent avec une certitude raisonnable dans quelle catégorie les différentes parties du code entrent. Si vous ne faites pas TDD, vous devriez au moins essayer d'écrire les tests les plus utiles.

Karl Bielefeldt
la source
1
Lorsque vous travaillez sur du code comme vous le suggérez avec votre exemple d'anticrénelage, je trouve que la meilleure chose est de développer le code expérimentalement, puis d'ajouter des tests de caractérisation pour m'assurer de ne pas casser accidentellement l'algorithme plus tard. Les tests de caractérisation sont très rapides et faciles à développer, donc la surcharge de travail est très faible.
Jules
1

Qu'il s'agisse de tests unitaires, de composants, d'intégration ou d'acceptation, l'important est qu'ils doivent être automatisés. Ne pas avoir de tests automatisés est une erreur fatale pour tout type de logiciel, des simples CRUD aux calculs les plus complexes. Le raisonnement est que l'écriture des tests automatisés coûtera toujours moins cher que le besoin continu d'exécuter tous les tests manuellement lorsque vous ne le faites pas, par ordre de grandeur. Après les avoir écrits, il vous suffit d'appuyer sur un bouton pour voir s'ils réussissent ou échouent. Les tests manuels prendront toujours beaucoup de temps à exécuter et dépendent des humains (les créatures vivantes qui s'ennuient, peuvent manquer d'attention, etc.) pour pouvoir vérifier si les tests réussissent ou échouent. En bref, écrivez toujours des tests automatisés.

Maintenant, sur la raison pour laquelle votre collègue pourrait être contre tout type de test unitaire sans TDD: c'est probablement parce qu'il est plus difficile de faire confiance aux tests écrits après le code de production. Et si vous ne pouvez pas faire confiance à vos tests automatisés, ils ne valent rien . Après le cycle TDD, vous devez d'abord faire échouer un test (pour la bonne raison) pour être autorisé à écrire le code de production pour le faire passer (et pas plus). Ce processus teste essentiellement vos tests, vous pouvez donc leur faire confiance. Sans oublier le fait que l'écriture de tests avant le code réel vous pousse à concevoir vos unités et composants pour être plus facilement testables (découplage élevé, application de la SRP, etc ...). Bien sûr, faire du TDD demande de la discipline .

Au lieu de cela, si vous écrivez tout le code de production en premier, lorsque vous en écrivez les tests, vous vous attendez à ce qu'ils passent au premier lancement. C'est très problématique, car vous avez peut-être créé un test qui couvre 100% de votre code de production, sans affirmer le bon comportement (peut même ne pas effectuer d'assertions! J'ai vu cela se produire ), car vous ne pouvez pas le voir échouer vérifiez d'abord s'il échoue pour la bonne raison. Ainsi, vous pouvez avoir des faux positifs. Les faux positifs finiront par briser la confiance dans votre suite de tests, obligeant essentiellement les gens à recourir à nouveau aux tests manuels, vous aurez donc le coût des deux processus (écriture de tests + tests manuels).

Cela signifie que vous devez trouver une autre façon de tester vos tests , comme le fait TDD. Vous avez donc recours au débogage, au commentaire de parties du code de production, etc., pour pouvoir faire confiance aux tests. Le problème est que le processus de "test de vos tests" est beaucoup plus lent de cette façon. Ajouter ce temps au temps que vous passerez à exécuter des tests ad hoc manuellement (car vous n'avez pas de tests automatiques pendant que vous codez le code de production), selon mon expérience, se traduit par un processus global beaucoup plus lent que la pratique TDD "par le livre" (Kent Beck - TDD par exemple). De plus, je suis prêt à faire un pari ici et à dire que vraiment "tester vos tests" après qu'ils ont été écrits demande beaucoup plus de discipline que TDD.

Ainsi, votre équipe peut peut-être reconsidérer les «raisons commerciales et non commerciales» de ne pas faire de TDD. D'après mon expérience, les gens ont tendance à penser que TDD est plus lent que de simplement écrire des tests unitaires une fois le code terminé. Cette hypothèse est erronée, comme vous l'avez lu ci-dessus.

MichelHenrich
la source
0

Souvent, la qualité des tests dépend de leur provenance. Je suis régulièrement coupable de ne pas faire de `` vrai '' TDD - j'écris du code pour prouver que la stratégie que j'aimerais utiliser fonctionne réellement, puis couvre chaque cas que le code est censé supporter avec des tests par la suite. Habituellement, l'unité de code est une classe, pour vous donner une idée générale de la quantité de travail que je ferai volontiers sans couverture de test - c'est-à-dire pas beaucoup. Cela signifie que la signification sémantique des tests correspond bien au système testé dans son état «fini» - parce que je les ai écrits en sachant quels cas le SUT remplit et comment il les remplit.

A l'inverse, TDD avec sa politique de refactoring agressif a tendance à rendre les tests obsolètes au moins aussi rapides que vous pouvez les écrire comme l'interface publique du système en cours de changement de test. Personnellement, je trouve la charge de travail mentale de la conception des unités fonctionnelles de l'application et de la synchronisation de la sémantique des tests qui la couvrent trop élevée pour maintenir ma concentration et la maintenance des tests glisse fréquemment. La base de code se termine par des tests qui ne testent rien de valeur ou sont tout simplement faux. Si vous avez la discipline et la capacité mentale nécessaires pour maintenir la suite de tests à jour, par tous les moyens, pratiquez le TDD aussi rigoureusement que vous le souhaitez. Je ne le fais pas, donc je l'ai trouvé moins réussi pour cette raison.

Tom W
la source
0

En fait, Oncle Bob a mentionné un point très intéressant dans l'une de ses vidéos Clean Coders. Il a dit que le cycle Red-Green-Refactor peut être appliqué de 2 manières.

Le premier est la méthode TDD conventionnelle. Écrivez un test qui échoue, puis passez le test et enfin refactorisez.

La deuxième façon est d'écrire un très petit morceau de code de production et de le suivre immédiatement par son test unitaire puis refactoriser.

L'idée est de faire de très petites étapes. Bien sûr, vous perdez la vérification du code de production que votre test est passé du rouge au vert, mais dans certains cas où je travaillais principalement avec des développeurs juniors qui refusaient même d'essayer de comprendre le TDD, il s'est avéré quelque peu efficace.

Encore une fois, je répète (et cela a été souligné par l'oncle Bob) que l'idée est de procéder par très petites étapes et de tester immédiatement le code de production qui vient d'être ajouté.

Songo
la source
"... l'idée est de faire de très petites étapes et de tester immédiatement le code de production qui vient d'être ajouté.": Je ne suis pas d'accord. Ce que vous décrivez est bon quand vous avez déjà une idée claire de ce que vous voulez faire et que vous voulez travailler sur les détails, mais vous devez d'abord avoir une vue d'ensemble. Sinon, en faisant de très petites étapes (tester, développer, tester, développer), vous pouvez vous perdre dans les détails. "Si vous ne savez pas où vous allez, vous risquez de ne pas y arriver."
Giorgio