Je comprends la valeur des tests automatisés et l’utilise chaque fois que le problème est suffisamment spécifié pour que je puisse proposer de bons scénarios de tests. J'ai toutefois remarqué que certaines personnes ici et sur StackOverflow insistent sur le fait de tester uniquement une unité, pas ses dépendances. Ici, je ne vois pas le bénéfice.
Le mocking / stubbing pour éviter de tester les dépendances ajoute de la complexité aux tests. Il ajoute des exigences de flexibilité / découplage artificielles à votre code de production pour prendre en charge les moqueries. (Je suis en désaccord avec quiconque dit que cela favorise une bonne conception. Écrire du code supplémentaire, introduire des choses telles que des frameworks d'injection de dépendance, ou ajouter de la complexité à votre base de code pour rendre les choses plus flexibles / plugables / extensibles / découplés sans réel cas d'utilisation, c'est trop d'ingénierie, bon design.)
Deuxièmement, tester les dépendances signifie que le code de bas niveau critique utilisé partout est testé avec des entrées autres que celles auxquelles l'auteur des tests a explicitement pensé. J'ai trouvé de nombreux bogues dans les fonctionnalités de bas niveau en effectuant des tests unitaires sur les fonctionnalités de haut niveau sans modifier la fonctionnalité de bas niveau dont elle dépendait. Idéalement, ces tests auraient été trouvés par les tests unitaires pour la fonctionnalité de bas niveau, mais les cas manqués se produisent toujours.
Quel est le côté opposé à cela? Est-il vraiment important qu'un test unitaire ne teste pas également ses dépendances? Si oui, pourquoi?
Edit: Je peux comprendre l’intérêt de moquer des dépendances externes telles que des bases de données, des réseaux, des services Web, etc. (Merci à Anna Lear de me motiver à clarifier cela.) Je parlais de dépendances internes , c’est-à-dire d’autres classes, de fonctions statiques, etc. qui n'ont pas de dépendances externes directes.
Réponses:
C'est une question de définition. Un test avec des dépendances est un test d'intégration, pas un test unitaire. Vous devriez également avoir une suite de tests d'intégration. La différence est que la suite de tests d'intégration peut être exécutée dans une autre structure de test et probablement pas dans le cadre de la construction, car elle prend plus de temps.
Pour notre produit: Nos tests unitaires sont exécutés à chaque construction, en quelques secondes. Un sous-ensemble de nos tests d'intégration s'exécute à chaque enregistrement, prenant 10 minutes. Notre suite d'intégration complète est exécutée chaque nuit et prend 4 heures.
la source
Tester avec toutes les dépendances en place est toujours important, mais c'est davantage dans le domaine des tests d'intégration, comme l'a dit Jeffrey Faust.
L'un des aspects les plus importants des tests unitaires consiste à rendre vos tests fiables. Si vous ne croyez pas qu'un test réussi signifie vraiment que les choses vont bien et qu'un test qui échoue signifie vraiment un problème dans le code de production, vos tests ne sont pas aussi utiles qu'ils peuvent l'être.
Afin de rendre vos tests dignes de confiance, vous devez faire plusieurs choses, mais je vais me concentrer sur une seule pour cette réponse. Vous devez vous assurer qu’ils sont faciles à exécuter, afin que tous les développeurs puissent les exécuter facilement avant d’archiver le code. «Facile à exécuter» signifie que vos tests s’exécutent rapidement et qu’aucune configuration ni installation exhaustive n’est nécessaire pour les faire fonctionner. Idéalement, tout le monde devrait pouvoir consulter la dernière version du code, lancer les tests immédiatement et les voir réussir.
Résumer les dépendances sur d’autres éléments (système de fichiers, base de données, services Web, etc.) vous permet d’éviter la configuration et vous rend moins vulnérable, ainsi que d’autres développeurs, à des situations dans lesquelles vous êtes tenté de dire: «Les tests ont échoué car je ne le fais pas. pas le partage réseau mis en place. Oh bien. Je les exécuterai plus tard. "
Si vous souhaitez tester ce que vous faites avec certaines données, vos tests unitaires pour ce code de logique métier ne doivent pas se soucier de la manière dont vous obtenez ces données. Pouvoir tester la logique de base de votre application sans dépendre d'éléments tels que des bases de données est génial. Si vous ne le faites pas, vous manquez.
PS Je devrais ajouter qu'il est tout à fait possible de trop d'ingénieurs au nom de la testabilité. Tester votre application aide à atténuer cela. Quoi qu'il en soit, une mauvaise mise en œuvre d'une approche ne la rend pas moins valide. Tout peut être mal utilisé et exagéré si on ne continue pas à demander "pourquoi est-ce que je fais ça?" en développant.
En ce qui concerne les dépendances internes, les choses deviennent un peu boueuses. J'aime penser que je veux protéger autant que possible ma classe des changements pour de mauvaises raisons. Si j'ai une configuration comme celle-ci,
Je ne me soucie généralement pas comment
SomeClass
est créé. Je veux juste l'utiliser. Si SomeClass change et nécessite maintenant des paramètres pour le constructeur ... ce n'est pas mon problème. Je ne devrais pas avoir à changer MyClass pour s'adapter à cela.Maintenant, cela ne concerne que la partie conception. En ce qui concerne les tests unitaires, je souhaite également me protéger des autres classes. Si je teste MyClass, j'aime bien savoir qu'il n'y a pas de dépendances externes, que SomeClass n'a pas, à un moment donné, introduit une connexion à une base de données ou un autre lien externe.
Mais un problème encore plus important est que je sais aussi que certaines des résultats de mes méthodes reposent sur les résultats de certaines méthodes de SomeClass. Sans me moquer / bousculer SomeClass, je n’aurais peut-être aucun moyen de modifier cet apport en fonction de la demande. Si j'ai de la chance, je pourrai peut-être composer mon environnement dans le test de manière à déclencher la bonne réponse de SomeClass, mais cette méthode introduit une complexité dans mes tests et les rend fragiles.
Réécrire MyClass pour accepter une instance de SomeClass dans le constructeur me permet de créer une fausse instance de SomeClass qui renvoie la valeur que je veux (via un framework moqueur ou avec une maquette manuelle). Je n'ai généralement pas à introduire une interface dans ce cas. Le faire ou non est à bien des égards un choix personnel qui peut être dicté par la langue de votre choix (par exemple, les interfaces sont plus susceptibles en C #, mais vous n’auriez certainement pas besoin de Ruby).
la source
Outre le problème de test unitaire et d'intégration, prenez en compte les éléments suivants.
Le widget de classe a des dépendances sur les classes Thingamajig et WhatsIt.
Un test unitaire pour Widget échoue.
Dans quelle classe se situe le problème?
Si vous avez répondu "lancer le débogueur" ou "lire le code jusqu'à ce que je le trouve", vous comprenez l'importance de tester uniquement l'unité, pas les dépendances.
la source
Imaginez que la programmation ressemble à la cuisine. Ensuite, le test unitaire équivaut à s'assurer que vos ingrédients sont frais, savoureux, etc. Alors que le test d'intégration, c'est comme s'assurer que votre repas est délicieux.
En fin de compte, vous assurer que votre repas est délicieux (ou que votre système fonctionne) est la chose la plus importante, le but ultime. Mais si vos ingrédients, je veux dire, les unités, fonctionnent, vous aurez un moyen moins coûteux de le savoir.
En effet, si vous pouvez garantir le fonctionnement de vos unités / méthodes, vous aurez plus de chances de disposer d’un système opérationnel. J'insiste sur "plus probable", par opposition à "certain". Vous avez toujours besoin de vos tests d'intégration, tout comme vous avez encore besoin de quelqu'un pour goûter le repas que vous avez cuisiné et vous dire que le produit final est bon. Vous aurez plus de facilité à y arriver avec des ingrédients frais, c'est tout.
la source
Tester les unités en les isolant complètement permettra de tester TOUTES les variations possibles des données et des situations auxquelles cette unité peut être soumise. Comme il est isolé du reste, vous pouvez ignorer les variations n’ayant pas d’effet direct sur l’unité à tester. cela diminuera considérablement la complexité des tests.
Tester avec différents niveaux de dépendances intégrés vous permettra de tester des scénarios spécifiques qui n’auraient peut-être pas été testés lors des tests unitaires.
Les deux sont importants. En faisant simplement des tests unitaires, vous obtiendrez invariablement des erreurs subtiles plus complexes lors de l’intégration de composants. Effectuer des tests d’intégration signifie que vous testez un système sans avoir la certitude que les différentes parties de la machine n’ont pas été testées. Il est presque impossible de penser que vous pouvez obtenir un meilleur test complet en effectuant un test d'intégration, car plus vous ajoutez de composants, plus le nombre de combinaisons d'entrées augmente TRES rapidement (pensez factoriel) et il devient très rapidement impossible de créer un test avec une couverture suffisante.
En bref, les trois niveaux de "tests unitaires" que j'utilise généralement dans presque tous mes projets:
L'injection de dépendance est un moyen très efficace d'y parvenir car elle permet d'isoler très facilement des composants pour les tests unitaires sans ajouter de complexité au système. Votre scénario commercial ne justifie peut-être pas l’utilisation du mécanisme d’injection, mais votre scénario de test le fera presque. Cela me suffit pour le rendre indispensable. Vous pouvez également les utiliser pour tester indépendamment les différents niveaux d'abstraction via des tests d'intégration partiels.
la source
Unité. Signifie singulier.
Tester 2 choses signifie que vous avez les deux choses et toutes les dépendances fonctionnelles.
Si vous ajoutez une 3ème chose, vous augmentez les dépendances fonctionnelles dans les tests au-delà de manière linéaire. Les interconnexions entre les choses se développent plus rapidement que le nombre de choses.
Il y a n (n-1) / 2 dépendances potentielles parmi n éléments testés.
C'est la grande raison.
La simplicité a de la valeur.
la source
Rappelez-vous comment vous avez appris à faire la récursion? Mon prof a dit "Supposons que vous avez une méthode qui fait x" (par exemple, résout le fibbonacci pour tout x). "Pour résoudre pour x il faut appeler cette méthode pour x-1 et x-2". Dans le même ordre d'idées, le remplacement des dépendances vous permet de prétendre qu'elles existent et de vérifier que l'unité actuelle fait ce qu'elle devrait faire. L’hypothèse est bien sûr que vous testez les dépendances de manière aussi rigoureuse.
Ceci est essentiellement le SRP au travail. Se concentrer sur une seule responsabilité, même pour vos tests, supprime la quantité de jonglage mental que vous devez faire.
la source
(Ceci est une réponse mineure. Merci @ Timwilliscroft pour l'indice.)
Les défauts sont plus faciles à localiser si:
Cela fonctionne bien sur le papier. Cependant, comme illustré dans la description de l'OP (les dépendances sont boguées), si les dépendances ne sont pas testées, il serait difficile de localiser l'emplacement du défaut.
la source
Beaucoup de bonnes réponses. J'aimerais aussi ajouter quelques autres points:
Le test unitaire vous permet également de tester votre code lorsque vos dépendances n'existent pas . Par exemple, vous ou votre équipe n'avez pas encore écrit les autres couches ou attendez peut-être une interface fournie par une autre société.
Les tests unitaires signifient également qu'il n'est pas nécessaire de disposer d'un environnement complet sur votre machine de développement (base de données, serveur Web, etc.). Je fortement recommander que tous les développeurs n'ont un tel environnement, coupé mais vers le bas , il est, pour les bugs repro etc. Cependant, si pour une raison quelconque , il est impossible d'imiter l'environnement de production , puis les tests unitaires au moins yu donne un certain niveau de confiance dans votre code avant de passer à un système de test plus grand.
la source
À propos de la conception: je pense que même les petits projets ont intérêt à rendre le code testable. Vous n’avez pas nécessairement besoin d’introduire quelque chose comme Guice (une classe d’usine simple le fera souvent), mais en séparant le processus de construction de la logique de programmation,
la source
Euh ... il y a de bons points sur les tests unitaires et d'intégration écrits dans ces réponses ici!
Les vues pratiques et liées aux coûts me manquent . Cela dit, je vois clairement l'intérêt de tests unitaires très isolés / atomiques (peut-être très indépendants les uns des autres et avec la possibilité de les exécuter en parallèle et sans aucune dépendance, comme les bases de données, les systèmes de fichiers, etc.) et les tests d'intégration (de niveau supérieur), mais ... c'est aussi une question de coûts (temps, argent, ...) et de risques .
Donc, il y a d'autres facteurs, qui sont beaucoup plus importants (comme "quoi tester" ) avant de penser à "comment tester" d' après mon expérience ...
Mon client paie-t-il (implicitement) la quantité supplémentaire d'écriture et de maintenance des tests? Une approche davantage axée sur les tests (écrire des tests avant d’écrire le code) est-elle vraiment rentable dans mon environnement (analyse risque / coût des défaillances du code, personnes, spécifications de conception, environnement de test configuré)? Le code est toujours bogué, mais pourrait-il être plus rentable de déplacer les tests vers l'utilisation en production (dans le pire des cas!)?
Cela dépend aussi beaucoup de la qualité de votre code (normes), des frameworks, de l'EDI, des principes de conception, etc. que vous et votre équipe suivez et de leur expérience. Un code bien écrit, facile à comprendre, suffisamment documenté (idéalement auto-documenté), modulaire, ... introduit probablement moins de bogues que l'inverse. Ainsi, le «besoin» réel, la pression ou les coûts / risques généraux de maintenance / correction de bugs pour des tests approfondis peuvent ne pas être élevés.
Passons à l'extrême lorsqu'un collègue de mon équipe a suggéré, nous devons essayer de tester un peu notre code de couche modèle Java EE pur avec une couverture souhaitée de 100% pour toutes les classes de la base de données. Ou le responsable qui souhaite que les tests d'intégration soient couverts à 100% par tous les cas d'utilisation réels et les flux de travail Web ui, car nous ne voulons pas risquer de faire échouer un cas d'utilisation. Mais nous avons un budget serré d'environ 1 million d'euros, un plan assez serré pour tout coder. Un environnement client, où les bugs potentiels des applications ne constitueraient pas un très grand danger pour les humains ou les entreprises. Notre application sera testée en interne avec des tests unitaires importants, des tests d'intégration, des tests clients clés avec des plans de test conçus, une phase de test, etc. Nous ne développons pas d'application pour une usine nucléaire ou la production pharmaceutique!
J'essaie moi-même d'écrire test si possible et de développer tout en testant mon code. Mais je le fais souvent à partir d'une approche descendante (tests d'intégration) et j'essaie de trouver le bon point, celui de créer la "couche d'application" pour les tests importants (souvent dans la couche modèle). (car il y a souvent beaucoup de "couches")
De plus, le code de test unitaire et d'intégration n'a pas un impact négatif sur le temps, l'argent, la maintenance, le codage, etc. Il est cool et devrait être appliqué, mais avec précaution et en tenant compte des implications lorsque vous démarrez ou après 5 ans d'un grand nombre de tests développés. code.
Donc, je dirais que cela dépend vraiment beaucoup de la confiance et de l’évaluation coûts / risques / avantages ... comme dans la vraie vie où vous ne pouvez pas et ne voulez pas courir avec beaucoup ou à 100% de mécanismes de sécurité.
L'enfant peut et doit monter quelque part et peut tomber et se blesser. La voiture peut cesser de fonctionner parce que j'ai fait le mauvais carburant (entrée non valide :)). Le pain grillé peut être brûlé si le bouton de l'heure cesse de fonctionner après 3 ans. Mais je n'ai pas, jamais, envie de conduire sur l'autoroute et de tenir mon volant détaché entre mes mains :)
la source