Est-il suffisant d'utiliser des tests d'acceptation et d'intégration au lieu de tests unitaires?

62

Courte introduction à cette question. J'ai utilisé maintenant TDD et dernièrement BDD pendant plus d'un an maintenant. J'utilise des techniques telles que la moquerie pour rendre mes tests plus efficaces. Dernièrement, j'ai lancé un projet personnel pour écrire un petit programme de gestion de l'argent pour moi-même. Comme je n'avais pas de code hérité, c'était le projet idéal pour commencer avec TDD. Malheureusement, je n’ai pas autant expérimenté la joie du TDD. Cela m'a même tellement gâché le plaisir que j'ai abandonné le projet.

Quel était le problème? Eh bien, j’ai utilisé l’approche de type TDD pour laisser les tests / exigences faire évoluer la conception du programme. Le problème était que plus de la moitié du temps de développement en ce qui concerne l'écriture / tests de refactorisation. Donc, au final, je ne voulais plus mettre en œuvre de fonctionnalités, car il me faudrait refactoriser et écrire sur de nombreux tests.

Au travail, j'ai beaucoup de code hérité. Ici, j'écris de plus en plus de tests d'intégration et d'acceptation et moins de tests unitaires. Cela ne semble pas être une mauvaise approche car les bogues sont principalement détectés par les tests d'acceptation et d'intégration.

Mon idée était que je pourrais finalement écrire plus de tests d'intégration et d'acceptation que de tests unitaires. Comme je l'ai dit pour détecter les bugs, les tests unitaires ne sont pas meilleurs que les tests d'intégration / acceptation. Les tests unitaires sont également bons pour la conception. Depuis que j'en écrivais beaucoup, mes cours sont toujours conçus pour être testables. De plus, l'approche consistant à laisser les tests / exigences guider la conception conduit dans la plupart des cas à une meilleure conception. Le dernier avantage des tests unitaires est qu'ils sont plus rapides. J'ai écrit suffisamment de tests d'intégration pour savoir qu'ils peuvent être presque aussi rapides que les tests unitaires.

Après avoir jeté un coup d’œil sur le Web, j’ai découvert qu’il existe des idées très similaires aux miennes mentionnées ici et . Que penses tu de cette idée?

Modifier

En répondant aux questions, un exemple où la conception était bonne, mais il me fallait une refactorisation considérable pour la prochaine exigence:

Au début, il était nécessaire d'exécuter certaines commandes. J'ai écrit un analyseur de commandes extensible - analysant les commandes à partir d'une sorte d'invite de commande et appelant celle qui convient sur le modèle. Les résultats ont été représentés dans une classe de modèle de vue: Premier design

Il n'y avait rien de mal ici. Toutes les classes étaient indépendantes les unes des autres et je pouvais facilement ajouter de nouvelles commandes, afficher de nouvelles données.

La prochaine exigence était que chaque commande ait sa propre représentation, une sorte d’aperçu du résultat de la commande. J'ai repensé le programme pour obtenir une meilleure conception pour la nouvelle exigence: Deuxième design

Cela était également utile, car chaque commande a désormais son propre modèle de vue et, par conséquent, son propre aperçu.

Le problème, c’est que l’analyseur de commandes a été modifié pour utiliser une analyse syntaxique des commandes basée sur un jeton et qu’il n’a plus la capacité d’exécuter les commandes. Chaque commande a son propre modèle de vue et le modèle de vue de données ne connaît que le modèle de vue de commande actuel, qui connaît les données à afficher.

Tout ce que je voulais savoir à ce stade-ci, c'est si la nouvelle conception ne répondait à aucune exigence existante. Je n'ai pas eu à changer AUCUN de mes tests d'acceptation. J'ai dû refacturer ou supprimer presque TOUS les tests unitaires, ce qui représentait un travail énorme.

Ce que je voulais montrer ici, c’est une situation courante qui s’est souvent produite au cours du développement. L'ancien ou le nouveau design ne posait pas de problème, ils changeaient naturellement avec les exigences - comment je l'ai compris, c'est l'un des avantages de TDD, que le design évolue.

Conclusion

Merci pour toutes les réponses et discussions. En résumé de cette discussion, j'ai réfléchi à une approche que je testerai avec mon prochain projet.

  • Tout d’abord, j’écris tous les tests avant de mettre en œuvre quoi que ce soit comme je l’ai toujours fait.
  • Pour les besoins, j’écris d’abord des tests de réception qui testent l’ensemble du programme. Ensuite, j'écris des tests d'intégration pour les composants sur lesquels je dois implémenter l'exigence. S'il existe un composant qui travaille en étroite collaboration avec un autre composant pour implémenter cette exigence, j'écrirais également des tests d'intégration dans lesquels les deux composants sont testés ensemble. Enfin et surtout, si je dois écrire un algorithme ou toute autre classe avec une permutation élevée - par exemple un sérialiseur -, j'écrirais des tests unitaires pour cette classe particulière. Toutes les autres classes ne sont pas testées, à l'exception des tests unitaires.
  • Pour les bogues, le processus peut être simplifié. Normalement un bug est causé par un ou deux composants. Dans ce cas, j'écrirais un test d'intégration pour les composants qui testent le bogue. Si cela concernait un algorithme, je n’écrirais qu’un test unitaire. S'il n'est pas facile de détecter le composant où le bogue survient, j'écrirais un test d'acceptation pour localiser le bogue - cela devrait être une exception.
Yggdrasil
la source
Ces questions semblent aborder davantage le problème de savoir pourquoi passer des tests. Je veux discuter de la possibilité d’écrire des tests fonctionnels plutôt que des tests unitaires.
Yggdrasil
Selon mes lectures, les réponses aux questions en double concernent principalement le moment où les tests non unitaires ont plus de sens
Gnat
Ce premier lien est lui-même un doublon. Vous pensez dire: programmers.stackexchange.com/questions/66480/…
Robbie Dee
Le lien de Robbie Dee répond encore plus à la question de savoir pourquoi tester.
Yggdrasil

Réponses:

37

C'est comparer des oranges et des pommes.

Tests d'intégration, tests de réception, tests unitaires, tests de comportement - ce sont tous des tests et ils vous aideront tous à améliorer votre code, mais ils sont également très différents.

Je vais passer en revue chacun des différents tests à mon avis et expliquer pourquoi j'espère que vous avez besoin d'un mélange de chacun d'entre eux:

Tests d'intégration:

Simplement, vérifiez que les différents composants de votre système s’intègrent correctement, par exemple, vous pouvez peut-être simuler une demande de service Web et vérifier que le résultat est renvoyé. J'utiliserais généralement des données statiques réelles (ish) et des dépendances simulées afin de pouvoir les vérifier de manière cohérente.

Tests d'acceptation:

Un test d'acceptation doit être directement lié à un cas d'utilisation métier. Cela peut être énorme ("les transactions sont soumises correctement") ou minuscule ("le filtre filtre avec succès une liste") - peu importe; ce qui compte, c’est qu’il soit explicitement lié à une exigence spécifique de l’utilisateur. J'aime me concentrer sur ceux-ci pour le développement piloté par les tests, car cela signifie que nous disposons d'un bon manuel de référence de tests sur les user stories à dev et à qa à vérifier.

Tests unitaires:

Pour les petites unités discrètes de fonctionnalités qui peuvent constituer ou non une histoire d'utilisateur individuelle - par exemple, une histoire d'utilisateur qui dit que nous récupérons tous les clients lorsque nous accédons à une page Web spécifique peut être un test d'acceptation (simuler la frappe sur le Web page et vérification de la réponse) mais peut également contenir plusieurs tests unitaires (vérifier que les autorisations de sécurité sont vérifiées, vérifier que la connexion à la base de données interroge correctement, vérifier que tout code limitant le nombre de résultats est exécuté correctement) - il s’agit de "tests unitaires" ce n'est pas un test d'acceptation complet.

Tests de comportement:

Définissez ce que le flux d'une application devrait être dans le cas d'une entrée spécifique. Par exemple, "lorsque la connexion ne peut pas être établie, vérifiez que le système tente à nouveau la connexion". Encore une fois, il est peu probable que ce soit un test d'acceptation complet, mais il vous permet néanmoins de vérifier quelque chose d'utile.

Celles-ci sont toutes, à mon avis, le fruit d'une longue expérience de la rédaction de tests Je n'aime pas me concentrer sur les approches de manuel, mais plutôt sur ce qui donne de la valeur à vos tests.

Michael
la source
Dans votre définition, je suppose que ce que je veux dire, c'est écrire plus de tests de comportement (?) Que de tests unitaires. Le test unitaire pour moi est un test qui teste une classe unique avec toutes les dépendances simulées. Il existe des cas où les tests unitaires sont les plus utiles. C'est par exemple lorsque j'écris un algorithme complexe. Ensuite, j'ai beaucoup d'exemples avec la sortie attendue de l'algorithme. Je veux tester ceci au niveau de l'unité parce que c'est en fait plus rapide que le test de comportement. Je ne vois pas l'intérêt de tester une classe au niveau de l'unité qui n'a qu'une main remplissant les chemins de la classe et pouvant facilement être testée par un test de comportement.
Yggdrasil
14
Personnellement, je pense que les tests d'acceptation sont le plus important, les tests de comportement sont importants pour tester des éléments tels que la communication, la fiabilité et les erreurs, et les tests unitaires sont importants pour tester de petites fonctionnalités complexes (un algorithme en serait un bon exemple)
Michael
Je ne suis pas si ferme avec votre terminologie. Nous programmons une suite de programmation. Là, je suis responsable de l'éditeur graphique. Mes tests testent l'éditeur avec des services fictifs du reste de la suite et avec une UI fictive. Quel genre de test serait-ce?
Yggdrasil
1
Dépend de ce que vous testez - testez-vous les fonctionnalités de l'entreprise (tests d'acceptation)? Est-ce que vous testez l'intégration (tests d'intégration)? Est-ce que vous testez ce qui se passe lorsque vous cliquez sur un bouton (tests de comportement)? Testez-vous des algorithmes (tests unitaires)?
Michael
4
"Je n'aime pas me concentrer sur les approches de manuel, mais plutôt sur ce qui donne de la valeur à vos tests" Oh, c'est vrai! La première question à toujours poser est "quel problème dois-je résoudre en faisant cela?". Et un projet différent peut avoir un problème différent à résoudre!
Laurent Bourgault-Roy
40

TL; DR: Tant que cela répond à vos besoins, oui.

Je fais du développement ATDD (Acceptance Test Driven Development) depuis de nombreuses années maintenant. Cela peut être très réussi. Il y a quelques choses à connaître.

  • Les tests unitaires aident vraiment à appliquer le CIO. Sans les tests unitaires, il incombe aux développeurs de s'assurer qu'ils répondent aux exigences d'un code bien écrit (dans la mesure où les tests unitaires conduisent à un code bien écrit)
  • Ils peuvent être plus lents et avoir de faux échecs si vous utilisez réellement des ressources dont on se moque.
  • Le test ne précise pas le problème spécifique comme le feraient les tests unitaires. Vous devez effectuer plus d'investigations pour résoudre les échecs de test.

Maintenant les avantages

  • Bien meilleure couverture de test, couvre les points d'intégration.
  • S'assure que le système dans son ensemble répond aux critères d'acceptation, ce qui est le but du développement logiciel.
  • Rend les grands refactors beaucoup plus faciles, plus rapides et moins chers.

Comme toujours, il vous appartient de faire l'analyse et de déterminer si cette pratique convient à votre situation. Contrairement à beaucoup de gens, je ne pense pas qu'il existe une bonne réponse idéalisée. Cela dépendra de vos besoins et exigences.

dietbuddha
la source
8
Excellent point. Il est trop facile de devenir un petit cadet de l'espace en matière de test et d'écrire des centaines de cas pour obtenir une lueur de satisfaction quand il "passe" avec brio. En termes simples: si votre logiciel ne fait pas ce qu’il doit faire du point de vue de l’UTILISATEUR, vous échouez au premier et au plus important des tests.
Robbie Dee
Bon point pour identifier le problème spécifique. Si j’ai un besoin énorme, j’écris des tests de réception qui testent l’ensemble du système, puis des tests qui testent les sous-tâches de certains composants du système pour répondre à l’exigence. Grâce à cela, je peux dans la plupart des cas identifier le composant où se trouve le défaut.
Yggdrasil
2
"Les tests unitaires aident à appliquer le CIO"?!? Je suis sûr que vous vouliez parler de DI là-bas au lieu d'IoC, mais enfin, pourquoi quelqu'un voudrait-il imposer l'utilisation de DI? Personnellement, je trouve que, dans la pratique, DI mène à des non-objets (programmation de type procédural).
Rogério
Pouvez-vous donner votre avis sur l’option (meilleure à l’OMI) de réaliser À LA FOIS l’intégration et les tests unitaires par rapport à l’argument consistant à ne faire que des tests d’intégration? Votre réponse est bonne, mais semble présenter ces choses comme s’excluant mutuellement, ce que je ne crois pas.
Starmandeluxe
@starmandeluxe Ils ne sont en effet pas mutuellement exclusifs. Il s’agit plutôt de la valeur que vous souhaitez tirer des tests. Je voudrais tester des unités partout où la valeur dépasse le coût de développement / support de l'écriture des tests unitaires. ex. Je voudrais certainement tester la fonction d’intérêt composé dans une application financière.
dietbuddha
18

Eh bien, j’ai utilisé l’approche de type TDD pour laisser les tests / exigences faire évoluer la conception du programme. Le problème était que plus de la moitié du temps de développement en ce qui concerne l’écriture / les tests de refactorisation

Les tests unitaires fonctionnent mieux lorsque l'interface publique des composants pour lesquels ils sont utilisés ne change pas trop souvent. Cela signifie que les composants sont déjà bien conçus (par exemple, en suivant les principes SOLID).

Il est donc erroné de croire qu'une bonne conception "évolue" en "lançant" de nombreux tests unitaires sur un composant. TDD n’est pas un "enseignant" pour une bonne conception, il ne peut qu’aider un peu à vérifier que certains aspects de la conception sont bons (notamment la testabilité).

Lorsque vos exigences changent et que vous devez modifier les composants internes d'un composant, ce qui fracturera 90% de vos tests unitaires, vous devrez donc les refactoriser très souvent. La conception n'était donc probablement pas aussi bonne.

Mon conseil est donc le suivant: réfléchissez à la conception des composants que vous avez créés et à la manière dont vous pouvez les rendre davantage en suivant le principe d'ouverture / fermeture. L’idée de ce dernier est de s’assurer que la fonctionnalité de vos composants peut être étendue ultérieurement sans la modifier (et donc ne pas casser l’API du composant utilisée par vos tests unitaires). De tels composants peuvent (et devraient être) couverts par des tests unitaires, et l'expérience ne devrait pas être aussi pénible que celle que vous avez décrite.

Lorsque vous ne pouvez pas concevoir une telle conception immédiatement, les tests d'acceptation et d'intégration peuvent s'avérer un meilleur départ.

EDIT: Parfois, la conception de vos composants peut être correcte, mais la conception de vos tests unitaires peut être source de problèmes . Exemple simple: vous voulez tester la méthode "MyMethod" de la classe X et écrire

    var x= new X();
    Assert.AreEqual("expected value 1" x.MyMethod("value 1"));
    Assert.AreEqual("expected value 2" x.MyMethod("value 2"));
    // ...
    Assert.AreEqual("expected value 500" x.MyMethod("value 500"));

(supposons que les valeurs ont un sens).

Supposons en outre que dans le code de production, il n’ya qu’un appel à X.MyMethod. Désormais, pour une nouvelle exigence, la méthode "MyMethod" nécessite un paramètre supplémentaire (par exemple, quelque chose du genre context), qui ne peut pas être omis. Sans tests unitaires, il faudrait refactoriser le code d'appel en un seul endroit. Avec les tests unitaires, il faut refactoriser 500 places.

Mais la cause ici n’est pas que l’unité se teste elle-même, c’est simplement le fait que le même appel à "X.MyMethod" est répété encore et encore, en ne suivant pas strictement le principe "Ne vous répétez pas (DRY)". La solution Ceci consiste à mettre les données de test et les valeurs attendues associées dans une liste et à exécuter en boucle les appels à "MyMethod" (ou, si l’outil de test prend en charge les "tests de lecteur de données", à utiliser cette fonctionnalité). le nombre de places à modifier dans les tests unitaires lorsque la signature de la méthode passe à 1 (au lieu de 500).

Dans votre cas réel, la situation pourrait être plus complexe, mais j'espère que vous en aurez une idée: lorsque vos tests unitaires utilisent une API de composants pour laquelle vous ne savez pas si elle peut être modifiée, veillez à en réduire le nombre. d'appels à cette API au minimum.

Doc Brown
la source
"Cela signifie que les composants sont déjà bien conçus.": Je suis d'accord avec vous, mais comment peut-on déjà concevoir les composants si vous écrivez les tests avant d'écrire le code, et que le code est le design? Au moins c'est ainsi que j'ai compris le TDD.
Giorgio
2
@Giorgio: en fait, peu importe que vous écriviez les tests en premier ou en aval. Concevoir signifie prendre des décisions sur la responsabilité d'un composant, sur l'interface publique, sur les dépendances (directes ou injectées), sur le comportement au moment de l'exécution ou de la compilation, sur la mutabilité, sur les noms, sur le flux de données, le flux de contrôle, les couches, etc. Bon concevoir signifie également reporter certaines décisions au dernier moment possible. Le test unitaire peut vous montrer indirectement si votre conception était correcte: si vous devez en refactoriser une grande partie par la suite lorsque les exigences changent, ce n’est probablement pas le cas.
Doc Brown
@Giorgio: un exemple pourrait clarifier: disons que vous avez un composant X avec une méthode "MyMethod" et 2 paramètres. En utilisant TDD, vous écrivez X x= new X(); AssertTrue(x.MyMethod(12,"abc"))avant d'implémenter réellement la méthode. En utilisant la conception initiale, vous pouvez écrire en class X{ public bool MyMethod(int p, string q){/*...*/}}premier et écrire les tests plus tard. Dans les deux cas, vous avez pris la même décision de conception. Si la décision était bonne ou mauvaise, TDD ne vous le dira pas.
Doc Brown
1
Je suis d'accord avec vous: je suis un peu sceptique lorsque je vois TDD appliqué à l'aveuglette, en supposant qu'il produira automatiquement un bon design. En outre, parfois TDD gêne si la conception n’est pas encore claire: je suis obligé de tester les détails avant d’avoir une vue d’ensemble de ce que je fais. Donc, si je comprends bien, nous sommes d’accord. Je pense que (1) les tests unitaires aident à vérifier une conception mais que la conception est une activité distincte et (2) le TDD n’est pas toujours la meilleure solution car vous devez organiser vos idées avant de commencer à écrire des tests et le TDD peut vous ralentir. cette.
Giorgio
1
En bref, les tests unitaires peuvent révéler des défauts dans la conception interne d'un composant. L'interface, les conditions préalables et postérieures doivent être connues à l'avance, sinon vous ne pouvez pas créer le test unitaire. La conception du composant doit donc être effectuée avant que le test unitaire puisse être écrit. La manière dont cela fonctionne - la conception de niveau inférieur, la conception détaillée ou la conception intérieure ou peu importe comment vous l'appelez - peut avoir lieu une fois que le test unitaire a été écrit.
Maarten Bodewes
9

Oui, Bien sur que c'est ça.

Considère ceci:

  • un test unitaire est un petit test ciblé qui permet d’exercer un petit morceau de code. Vous en écrivez beaucoup pour obtenir une couverture de code décente, de sorte que tous (ou la majorité des bits difficiles) soient testés.
  • un test d'intégration est une grande partie de test qui exploite une grande surface de votre code. Vous en écrivez quelques-uns pour obtenir une couverture de code décente, de sorte que tous (ou la majorité des bits difficiles) soient testés.

Voir la différence globale ....

Il s’agit d’un problème de couverture de code. Si vous pouvez réaliser un test complet de tout votre code à l’aide des tests d’intégration / acceptation, il n’ya pas de problème. Votre code est testé. C'est le but.

Je pense que vous aurez peut-être besoin de les mélanger, car chaque projet basé sur TDD nécessitera des tests d'intégration pour s'assurer que toutes les unités fonctionnent bien ensemble (je sais par expérience qu'une base de code testée à 100% réussie ne fonctionne pas nécessairement quand vous les mettez tous ensemble!)

Le problème réside vraiment dans la facilité des tests, du débogage des défaillances et de leur résolution. Certaines personnes trouvent que leurs tests unitaires sont très efficaces, elles sont petites et simples et les échecs sont faciles à voir, mais le désavantage est que vous devez réorganiser votre code pour l'adapter aux outils de test unitaire et en écrire un très grand nombre. Un test d'intégration est plus difficile à écrire pour couvrir beaucoup de code, et vous devrez probablement utiliser des techniques comme la journalisation pour déboguer les échecs (bien que, je dirais que vous devez le faire de toute façon, vous ne pouvez pas effectuer d'échec de test unitaire. quand sur place!).

Quoi qu'il en soit, vous obtenez toujours du code testé, il vous suffit de décider quel mécanisme vous convient le mieux. (Je ferais un peu du mixage, testera les algorithmes complexes et intégrera le reste).

gbjbaanb
la source
7
Pas tout à fait vrai ... Avec les tests d'intégration, il est possible d'avoir deux composants qui sont tous les deux bogués, mais leurs bogues disparaissent dans le test d'intégration. Peu importe jusqu'à ce que l'utilisateur final l'utilise de manière à ce qu'un seul de ces composants soit utilisé ...
Michael Shaw
1
Couverture de code! = Testé - à part les bugs qui s'annulent, qu'en est-il des scénarios auxquels vous n'avez jamais pensé? Les tests d'intégration conviennent pour les tests de trajectoire sans problème, mais je vois rarement des tests d'intégration adéquats lorsque les choses ne se passent pas bien.
Michael
4
@Ptolemy Je pense que la rareté de 2 composants buggés s'annulant l'un l'autre est bien inférieure à celle de 2 composants actifs interférant l'un avec l'autre.
Gbjbaanb
2
@ Michael, alors vous n'avez pas mis assez d'effort dans les tests, j'ai dit qu'il était plus difficile de faire de bons tests d'intégration car les tests devaient être beaucoup plus détaillés. Vous pouvez fournir des données erronées dans un test d'intégration aussi facilement que dans un test unitaire. Tests d'intégration! = Bonne voie. Il s’agit d’exercer le plus de code possible, c’est la raison pour laquelle des outils de couverture de code vous indiquent la quantité de code que vous avez exercée.
Gbjbaanb
1
@Michael Lorsque j'utilise correctement des outils comme Cucumber ou SpecFlow, je peux créer des tests d'intégration qui testent également les exceptions et les situations extrêmes aussi rapidement que les tests unitaires. Mais je suis d'accord si une classe a trop de permutations, je préfère écrire un test unitaire pour cette classe. Mais ce sera moins souvent le cas que d'avoir des classes avec seulement quelques chemins.
Yggdrasil
2

Je pense que c'est une idée horrible.

Comme les tests d'acceptation et les tests d'intégration touchent des portions plus larges de votre code pour tester une cible spécifique, ils auront besoin de plus de refactorisation au fil du temps, pas moins. Pire encore, dans la mesure où ils couvrent de larges sections du code, ils augmentent le temps que vous passez à rechercher la cause du problème car vous avez une zone de recherche plus étendue.

Non, vous devriez généralement écrire plus de tests unitaires, sauf si vous avez une application étrange qui est 90% UI ou quelque chose d'autre qui est difficile à tester. La douleur que vous rencontrez ne provient pas des tests unitaires, mais du développement du premier test. En règle générale, vous ne devriez consacrer qu’un tiers de votre temps à la plupart des tests d’écriture. Après tout, ils sont là pour vous servir, pas l'inverse.

Telastyn
la source
2
Le principal bœuf que j'entends contre le TDD est que cela perturbe le flux de développement naturel et impose la programmation défensive dès le début. Si un programmeur est déjà pressé par le temps, il est probable qu'il veuille simplement couper le code et le peaufiner plus tard. Bien entendu, respecter un délai arbitraire avec un code erroné est une fausse économie.
Robbie Dee
2
En effet, d'autant plus que "peaufiner plus tard" ne semble jamais se produire réellement - chaque critique que je fais lorsqu'un développeur insiste pour que le message "il faut sortir, nous le ferons plus tard" quand nous savons tous que cela ne va pas se produire - technique dette = développeurs en faillite à mon avis.
Michael
3
La réponse est logique pour moi, je ne sais pas pourquoi il a eu tant de points négatifs. Mike Cohn a déclaré: «Les tests unitaires doivent constituer le fondement d’une stratégie solide d’automatisation des tests et, en tant que tels, ils représentent la plus grande partie de la pyramide. Les tests unitaires automatisés sont merveilleux car ils fournissent des données spécifiques à un programmeur. ligne 47 " mountaingoatsoftware.com/blog/…
guillaume31
4
@ guillaume31 Ce n'est pas parce qu'un gars a dit une fois qu'ils sont bons qu'ils ne sont bons. D'après mon expérience, les bogues ne sont PAS détectés par les tests unitaires, car ils ont été modifiés pour répondre aux nouvelles exigences. De plus, la plupart des bogues sont des bogues d'intégration. C'est pourquoi je détecte la plupart d'entre eux avec mes tests d'intégration.
Yggdrasil
2
@Yggdrasil Je pourrais aussi citer Martin Fowler: "Si vous rencontrez un échec lors d'un test de haut niveau, vous n'avez pas seulement un bug dans votre code fonctionnel, vous avez également un test unitaire manquant". martinfowler.com/bliki/TestPyramid.html Quoi qu'il en soit, si les tests d'intégration fonctionnent seuls pour vous, alors tout va bien. D'après mon expérience, même s'ils sont nécessaires, ils sont plus lents, délivrent des messages d'échec moins précis et moins manoeuvrables (plus combinatoires) que les tests unitaires. De plus, j’ai tendance à être moins sûr de l’avenir lorsque j’écris des tests d’intégration (raisonnement sur des scénarios prédéfinis) plutôt que sur l’exactitude d’un objet en tant que tel.
guillaume31
2

Le "gain" avec TDD est qu’une fois les tests écrits, ils peuvent être automatisés. Le revers de la médaille est qu'il peut consommer une grande partie du temps de développement. Que cela ralentisse réellement l'ensemble du processus est discutable. L'argument étant que les tests initiaux réduisent le nombre d'erreurs à corriger à la fin du cycle de développement.

C'est là que BDD intervient car les comportements peuvent être inclus dans les tests unitaires, de sorte que le processus est par définition moins abstrait et plus concret.

De toute évidence, si vous disposiez d’un temps infini, vous feriez autant de tests que possible de différentes variétés. Cependant, le temps est généralement limité et les tests continus ne sont rentables que jusqu'à un certain point.

Tout cela nous amène à la conclusion que les tests offrant le plus de valeur devraient être au premier plan du processus. Cela en soi ne favorise pas automatiquement un type de test par rapport à un autre - plus que chaque cas doit être examiné selon ses mérites.

Si vous écrivez un widget de ligne de commande pour un usage personnel, les tests unitaires vous intéresseront principalement. Alors qu’un service Web, par exemple, nécessiterait une quantité importante de tests d’intégration / comportementaux.

Alors que la plupart des types de tests se concentrent sur ce que l’on pourrait appeler la «ligne de course», c’est-à-dire ce qui est demandé par l’entreprise, les tests unitaires sont excellents pour éliminer les bugs subtils qui pourraient faire surface au cours des phases de développement ultérieures. Comme il s'agit d'un avantage difficilement mesurable, il est souvent négligé.

Robbie Dee
la source
1
J'écris mes tests au début et j'écris suffisamment de tests pour couvrir la plupart des erreurs. Quant aux insectes qui refont surface plus tard. Cela me semble qu'une exigence a changé ou qu'une nouvelle exigence entre en jeu. Ensuite, les tests d'intégration / comportement doivent être modifiés, a ajouté. Si un bogue apparaît dans une ancienne exigence, mon test le montrera. Quant à l'automatisation. tous mes tests courent tout le temps.
Yggdrasil
L'exemple auquel je pensais dans le dernier paragraphe est celui où, par exemple, une bibliothèque était utilisée exclusivement par une seule application, mais il y avait alors une exigence commerciale pour en faire une bibliothèque à usage général. Dans ce cas, il serait peut-être préférable de faire au moins quelques tests unitaires plutôt que d'écrire de nouveaux tests d'intégration / de comportement pour chaque système que vous attachez à la bibliothèque.
Robbie Dee
2
Les tests automatisés et les tests unitaires sont totalement orthogonaux. Tout projet qui se respecte aura une intégration automatisée et des tests fonctionnels. Certes, vous ne voyez pas souvent les tests unitaires manuels, mais ils peuvent exister (fondamentalement, le test manuel unifié est un utilitaire de test pour certaines fonctionnalités spécifiques).
Jan Hudec
Bien en effet. Il existe un marché florissant pour les outils tiers automatisés qui existent en dehors de la sphère du développement depuis un temps considérable.
Robbie Dee
1

Le dernier avantage des tests unitaires est qu'ils sont plus rapides. J'ai écrit suffisamment de tests d'intégration pour savoir qu'ils peuvent être presque aussi rapides que les tests unitaires.

C'est le point clé, et pas seulement "le dernier avantage". Lorsque le projet devient de plus en plus important, vos tests d'acceptation d'intégration deviennent de plus en plus lents. Et ici, je veux dire si lentement que vous allez arrêter de les exécuter.

Bien sûr, les tests unitaires sont également de plus en plus lents, mais ils sont toujours plus rapides. Par exemple, dans mon projet précédent (c ++, quelques 600 kLOC, 4000 tests unitaires et 200 tests d’intégration), il fallait environ une minute pour exécuter tous les tests et plus de 15 pour exécuter les tests d’intégration. Construire et exécuter des tests unitaires pour la pièce à modifier prendrait en moyenne moins de 30 secondes. Lorsque vous pouvez le faire si vite, vous aurez envie de le faire tout le temps.

Soyons clairs: je ne dis pas qu'il ne faut pas ajouter de tests d'intégration et d'acceptation, mais il semblerait que vous ayez mal interprété TDD / BDD.

Les tests unitaires sont également bons pour la conception.

Oui, concevoir en gardant à l’esprit la testabilité améliorera la conception.

Le problème était que plus de la moitié du temps de développement en ce qui concerne l'écriture / tests de refactorisation. Donc, au final, je ne voulais plus mettre en œuvre de fonctionnalités, car il me faudrait refactoriser et écrire sur de nombreux tests.

Eh bien, lorsque les exigences changent, vous devez changer le code. Je vous dirais que vous n’avez pas terminé votre travail si vous n’écrivez pas de tests unitaires. Mais cela ne signifie pas que vous devriez avoir une couverture de 100% avec les tests unitaires - ce n'est pas l'objectif. Certaines choses (comme l'interface graphique, ou l'accès à un fichier, ...) ne sont même pas destinées à être testées à l'unité.

Le résultat est une meilleure qualité de code et une autre couche de tests. Je dirais que ça vaut le coup.


Nous avons également passé plusieurs milliers de tests d’acceptation, et il faudrait une semaine entière pour tout exécuter.

BЈовић
la source
1
Avez-vous regardé mon exemple? C'est arrivé tout le temps. Le fait est que, lorsque j'implémente une nouvelle fonctionnalité, je modifie / ajoute le test unitaire afin qu'il teste la nouvelle fonctionnalité - donc aucun test unitaire ne sera rompu. Dans la plupart des cas, les effets secondaires des modifications ne sont pas détectés par les tests unitaires, car l'environnement est simulé. A cause de cela, aucun test unitaire ne m'a jamais dit que j'avais cassé une fonctionnalité existante. Ce sont toujours les tests d'intégration et d'acceptation qui m'ont montré mes erreurs.
Yggdrasil
En ce qui concerne le temps d'exécution. Avec une application en croissance, j'ai principalement un nombre croissant de composants isolés. Sinon, j'ai fait quelque chose de mal. Lorsque j'implémente une nouvelle fonctionnalité, celle-ci ne concerne généralement qu'un nombre limité de composants. J'écris un ou plusieurs tests d'acceptation dans le cadre de l'application entière, ce qui pourrait être lent. De plus, j'écris les mêmes tests du point de vue des composants - ces tests sont rapides, car les composants sont généralement rapides. Je peux exécuter les tests de composants à tout moment, car ils sont assez rapides.
Yggdrasil
@Yggdrasil Comme je l'ai dit, les tests unitaires ne sont pas tous puissants, mais ils constituent généralement la première couche de tests, car ils sont les plus rapides. D'autres tests sont également utiles et doivent être combinés.
Février
1
Ce n’est pas parce qu’ils sont plus rapides qu’ils doivent être utilisés uniquement pour cette raison ou parce qu’il est courant de les écrire. Comme je l'ai dit, mes tests unitaires ne se cassent pas - ils n'ont donc aucune valeur pour moi.
Yggdrasil
1
La question était: quelle valeur ai-je du test unitaire, quand ils ne cassent pas? Pourquoi s'embêter à les écrire alors que je dois toujours les adapter aux nouvelles exigences? Je ne vois que leur valeur concerne les algorithmes et les autres classes à permutation élevée. Mais ce sont moins que les tests de composant et d’acceptation.
Yggdrasil