J'ai été chargé d'écrire des tests unitaires pour une application existante. Après avoir terminé mon premier fichier, j'ai 717 lignes de code de test pour 419 lignes de code original.
Ce ratio va-t-il devenir ingérable si nous augmentons la couverture de notre code?
Ma compréhension des tests unitaires consistait à tester chaque méthode de la classe pour s'assurer que chaque méthode fonctionnait comme prévu. Cependant, dans la demande d'attraction, mon responsable technique a indiqué que je devrais me concentrer sur des tests de niveau supérieur. Il a suggéré de tester 4-5 cas d'utilisation les plus couramment utilisés avec la classe en question, plutôt que de tester chaque fonction de manière exhaustive.
Je fais confiance au commentaire de mon responsable technique. Il a plus d'expérience que moi et son instinct est meilleur lorsqu'il s'agit de concevoir des logiciels. Mais comment une équipe composée de plusieurs personnes rédige-t-elle des tests pour une norme aussi ambiguë? Autrement dit, comment puis-je connaître mes pairs et partager la même idée pour les "cas d'utilisation les plus courants"?
Pour moi, une couverture de 100% des tests unitaires est un objectif ambitieux, mais même si nous n'atteignions que 50%, nous saurions que 100% de ces 50% étaient couverts. Sinon, écrire des tests pour une partie de chaque fichier laisse beaucoup de place pour tricher.
la source
Réponses:
Oui, avec une couverture de 100%, vous passerez des tests inutiles. Malheureusement, le seul moyen fiable de déterminer les tests dont vous n'avez pas besoin est de les écrire tous, puis d'attendre environ 10 ans pour voir ceux qui n'ont jamais échoué.
Maintenir de nombreux tests n'est généralement pas problématique. De nombreuses équipes ont automatisé l'intégration et les tests système en plus d'une couverture de 100% des tests unitaires.
Cependant, vous n'êtes pas en phase de test de maintenance, vous rattrapez votre retard. Il est bien mieux d'avoir 100% de vos cours à 50% de couverture de test que 50% de vos cours à 100% de couverture de test, et votre chef de file semble essayer de vous amener à répartir votre temps en conséquence. Une fois que vous avez cette base de référence, l'étape suivante consiste généralement à appliquer 100% des fichiers qui sont modifiés à l'avenir.
la source
Si vous avez travaillé sur des bases de code volumineuses créées à l'aide de Test Driven Development, vous savez déjà que trop de tests unitaires peuvent exister. Dans certains cas, l’essentiel des efforts de développement consiste à mettre à jour des tests de qualité médiocre qui seraient mieux implémentés sous forme de contrôles invariants, de précondition et de post-condition dans les classes appropriées, au moment de l’exécution (c’est-à-dire comme effet secondaire d’un test de niveau supérieur )
Un autre problème est la création de conceptions de mauvaise qualité, utilisant des techniques de conception axées sur le transport du fret, qui entraînent une prolifération de choses à tester (plus de classes, d'interfaces, etc.). Dans ce cas, le fardeau peut sembler être la mise à jour du code de test, mais le vrai problème est une conception de mauvaise qualité.
la source
Réponses à vos questions
Bien sûr ... Vous pouvez, par exemple, avoir plusieurs tests qui semblent être différents à première vue mais qui testent réellement la même chose (dépendent logiquement des mêmes lignes de code d'application "intéressant" à tester).
Vous pouvez également tester les éléments internes de votre code qui ne font jamais surface (c’est-à-dire qui ne font pas partie d’un contrat d’interface), où l’on pourrait se demander si cela a un sens ou non. Par exemple, la formulation exacte des messages du journal interne ou autre.
Cela me semble tout à fait normal. Vos tests passent beaucoup de lignes de code sur la configuration et le démontage en plus des tests réels. Le rapport peut améliorer ou ne pas améliorer. Je suis moi-même assez expérimenté dans les tests et j'investis souvent plus de temps que de code dans les tests.
Le ratio ne prend pas en compte autant. Il existe d’autres qualités d’essais qui ont tendance à les rendre ingérables. Si vous devez régulièrement refactoriser tout un tas de tests lorsque vous apportez des modifications assez simples à votre code, vous devriez examiner de près les raisons. Et ce ne sont pas le nombre de lignes que vous avez, mais comment vous abordez le codage des tests.
Ceci est correct pour les tests "unitaires" au sens strict. Ici, "unité" est quelque chose comme une méthode ou une classe. Le but du test "d'unité" est de ne tester qu'une unité de code spécifique, et non l'ensemble du système. Idéalement, vous supprimeriez tout le reste du système (en utilisant des doubles ou autres).
Ensuite, vous êtes tombé dans le piège de supposer que les gens signifiaient en fait des tests unitaires quand ils disaient des tests unitaires. J'ai rencontré beaucoup de programmeurs qui disent "test unitaire" mais veulent dire quelque chose de très différent.
Bien sûr, se concentrer uniquement sur les 80% de code les plus importants réduit également la charge. J'apprécie que vous accordiez une grande importance à votre patron, mais cela ne me semble pas être le choix optimal.
Je ne sais pas ce que "couverture de test unitaire" est. Je suppose que vous voulez dire "couverture de code", c'est-à-dire qu'après l'exécution de la suite de tests, chaque ligne de code (= 100%) a été exécutée au moins une fois.
Il s’agit d’une métrique approximative, mais de loin pas la meilleure norme pour laquelle on puisse tirer. Le simple fait d’exécuter des lignes de code n’est pas une image complète; Cela ne représente pas, par exemple, différents chemins à travers des branches compliquées et imbriquées. Il s’agit plutôt d’une mesure qui pointe du doigt des morceaux de code trop peu testés (évidemment, si une classe a une couverture de code de 10% ou 5%, alors quelque chose ne va pas); Par contre, une couverture de 100% ne vous dira pas si vous avez suffisamment testé ou correctement.
Test d'intégration
Cela m'agace énormément lorsque , par défaut, les gens parlent constamment de tests unitaires . À mon avis (et mon expérience), le test unitaire est excellent pour les bibliothèques / API; Dans les domaines plus orientés vers les entreprises (où nous parlons de cas d'utilisation comme dans la question à traiter), ils ne sont pas nécessairement la meilleure option.
Pour le code général des applications et dans l’entreprise moyenne (gagner de l’argent, respecter les délais et satisfaire la satisfaction de la clientèle sont importants, et vous voulez principalement éviter les bogues qui se présentent directement à l’utilisateur, ou qui pourraient entraîner de véritables catastrophes - nous ne sommes pas parler de lancements de fusées de la NASA ici), les tests d’intégration ou de fonctionnalité sont beaucoup plus utiles.
Ceux-ci vont de pair avec le développement basé sur le comportement ou le développement basé sur les fonctionnalités; ceux-ci ne fonctionnent pas avec des tests unitaires (stricts), par définition.
Pour faire court (ish), un test d’intégration / fonctionnalité applique toute la pile d’applications. Dans une application basée sur le Web, il agirait comme un navigateur en cliquant par l'application (et non, évidemment il n'a pas a être que simpliste, il existe des cadres très puissants là - bas pour le faire - visitez http: // concombre. io pour un exemple).
Oh, pour répondre à vos dernières questions: vous obtenez une couverture de test élevée pour toute votre équipe en vous assurant qu'une nouvelle fonctionnalité n'est programmée que lorsque son test de fonctionnalité a été mis en œuvre et qu'il a échoué. Et oui, cela signifie toutes les fonctionnalités. Cela vous garantit une couverture de 100% (positive) des fonctionnalités. Il garantit par définition qu’une fonctionnalité de votre application ne «disparaîtra jamais». Il ne garantit pas une couverture de code à 100% (par exemple, sauf si vous programmez activement des fonctions négatives, vous n'exercerez pas votre traitement des erreurs / traitement des exceptions).
Cela ne vous garantit pas une application sans bug; vous voudrez bien sûr rédiger des tests de fonctionnalité pour les situations de bogue évidentes ou très dangereuses, les erreurs de saisie de l'utilisateur, le piratage (par exemple, la gestion de session, la sécurité, etc.); Toutefois, même la programmation des tests positifs présente un avantage considérable et est tout à fait réalisable avec des cadres modernes et puissants.
Les tests de fonctionnalités / d'intégration ont évidemment leur propre boîte de dialogue (par exemple, les performances; les tests redondants des frameworks tiers; puisque vous n'utilisez généralement pas les doublons, ils ont également tendance à être plus difficile à écrire, selon mon expérience ...), mais je ' d prenez une application testée à 100% avec des fonctionnalités positives sur une application testée à 100% par unités de couverture de code (pas de bibliothèque!) tous les jours.
la source
Oui, il est possible d'avoir trop de tests unitaires. Si vous avez une couverture à 100% avec des tests unitaires et aucun test d'intégration, par exemple, le problème est clair.
Quelques scénarios:
Vous modifiez vos tests avec une implémentation spécifique. Ensuite, vous devez vous débarrasser des tests unitaires lorsque vous effectuez une refactorisation, pour ne pas dire lorsque vous modifiez la mise en œuvre (un problème très fréquent lors de l'optimisation des performances).
Un bon équilibre entre les tests unitaires et les tests d'intégration réduit ce problème sans perdre une couverture significative.
Vous pourriez avoir une couverture raisonnable pour chaque commit avec 20% des tests que vous avez, laissant les 80% restants pour l'intégration ou au moins des tests séparés; Les principaux effets négatifs que vous voyez dans ce scénario sont des changements lents car vous devez attendre longtemps pour que les tests soient exécutés.
Vous modifiez trop le code pour vous permettre de le tester; Par exemple, j'ai constaté de nombreux abus d'IoC sur des composants qui ne nécessiteront jamais de modifications ou du moins il est coûteux et peu prioritaire de les généraliser, mais les utilisateurs passent beaucoup de temps à les généraliser et à les reformuler pour permettre les tests unitaires. .
Je suis tout particulièrement d'accord avec la suggestion d'obtenir une couverture de 50% sur 100% des fichiers, au lieu d'une couverture de 100% sur 50% des fichiers; concentrez vos efforts initiaux sur les cas positifs les plus courants et les cas les plus dangereux, n'investissez pas trop dans la gestion des erreurs et les chemins inhabituels, non pas parce qu'ils ne sont pas importants, mais parce que vous avez un temps limité et un univers de tests infini, il faut donc donner la priorité à chaque cas.
la source
N'oubliez pas que chaque test a un coût et un avantage. Les inconvénients incluent:
Si les coûts dépassent les avantages, il vaut mieux un test non écrit. Par exemple, si les fonctionnalités sont difficiles à tester, si l'API change souvent, si la correction est relativement peu importante et si le test détecte un défaut est faible, il est probablement préférable de ne pas l'écrire.
En ce qui concerne votre ratio de tests par code, si le code est suffisamment dense en logique, ce ratio peut être garanti. Cependant, il ne vaut probablement pas la peine de maintenir un rapport aussi élevé dans une application typique.
la source
Oui, il existe trop de tests unitaires.
Bien que le test soit bon, chaque test unitaire est:
Une charge de maintenance potentielle étroitement liée à l'API
Temps qui pourrait être consacré à autre chose
Il est sage de viser une couverture de code à 100%, mais cela signifie loin une suite de tests dont chacun fournit indépendamment une couverture de code à un point d'entrée spécifié (fonction / méthode / appel, etc.).
Même s’il est difficile d’obtenir une bonne couverture et de dissuader les bogues, il existe probablement «autant de tests unitaires erronés» que de «trop de tests unitaires».
La pragmatique pour la plupart des codes indique:
Assurez-vous d'avoir une couverture à 100% des points d'entrée (tout est testé d'une manière ou d'une autre) et visez une couverture de code proche de 100% des chemins «non-erreurs».
Testez toutes les valeurs ou tailles min / max pertinentes
Testez tout ce que vous pensez être un cas spécial amusant, en particulier des valeurs "impaires".
Lorsque vous trouvez un bogue, ajoutez un test unitaire qui l'aurait révélé et réfléchissez à la possibilité d'ajouter des cas similaires.
Pour des algorithmes plus complexes, considérez également:
Par exemple, vérifiez un algorithme de tri avec une entrée aléatoire et la validation des données est triée à la fin en le scannant.
Je dirais que votre responsable technique propose des tests «minimaux avec des fesses nues». Je propose des «tests de qualité maximale» et il existe un spectre entre les deux.
Votre supérieur sait peut-être que le composant que vous construisez sera intégré dans une pièce plus grande et que l'unité sera testée plus en profondeur une fois intégrée.
La leçon clé consiste à ajouter des tests lorsque des bogues sont trouvés. Ce qui me mène à ma meilleure leçon sur le développement de tests unitaires:
Concentrez-vous sur les unités, pas les sous-unités. Si vous construisez une unité à partir de sous-unités, écrivez des tests très basiques pour les sous-unités jusqu'à ce qu'elles soient plausibles et atteignez une meilleure couverture en testant les sous-unités via leurs unités de contrôle.
Donc, si vous écrivez un compilateur et que vous devez écrire une table de symboles (par exemple). Obtenez la table de symboles opérationnelle avec un test de base, puis travaillez sur (par exemple) l'analyseur de déclaration qui remplit la table. Ajoutez des tests supplémentaires à l'unité 'autonome' de la table des symboles si vous y trouvez des bogues. Sinon, augmentez la couverture par des tests unitaires sur l'analyseur de déclaration, puis sur l'ensemble du compilateur.
Cela donne le meilleur rapport qualité-prix (un des tests dans son ensemble consiste à tester plusieurs composants) et laisse plus de possibilités de re-conception et d’affinement, car seule l’interface «externe» est utilisée dans les tests qui ont tendance à être plus stables.
Couplé aux conditions préalables de test du code de débogage, des post-conditions comprenant des invariants à tous les niveaux, vous bénéficiez d'une couverture de test maximale pour une implémentation de test minimale.
la source
Premièrement, avoir plus de lignes de test que de code de production ne pose pas nécessairement problème . Le code de test est (ou devrait être) linéaire et facile à comprendre - sa complexité nécessaire est très très basse, que le code de production le soit ou non. Si la complexité des tests commence à s'approcher de celle du code de production, vous avez probablement un problème.
Oui, il est possible d'avoir trop de tests unitaires - une simple expérience montre que vous pouvez continuer à ajouter des tests qui n'apportent aucune valeur supplémentaire et que tous ces tests ajoutés peuvent empêcher au moins certains refactorings.
L'avis de ne tester que les cas les plus courants est erroné, à mon avis. Ceux-ci peuvent servir de tests de détection de la fumée pour gagner du temps, mais les tests vraiment précieux détectent des cas difficiles à appliquer dans l’ensemble du système. Par exemple, une injection d'erreur contrôlée des échecs d'allocation de mémoire peut être utilisée pour mettre en œuvre des chemins de récupération qui, autrement, pourraient être de qualité totalement inconnue. Vous pouvez également indiquer la valeur zéro en tant que valeur dont vous savez qu'il sera utilisé comme diviseur (ou un nombre négatif qui aura une racine carrée), et assurez-vous de ne pas obtenir une exception non gérée.
Les tests suivants les plus précieux sont ceux qui exercent les limites extrêmes ou les points limites. Par exemple, une fonction qui accepte les mois de l'année (basés sur 1) doit être testée avec les valeurs 0, 1, 12 et 13, de sorte que vous sachiez que les transitions valide-invalide sont au bon endroit. Il est exagéré d'utiliser également la version 2..11 pour ces tests.
Vous êtes dans une position difficile, en ce sens que vous devez écrire des tests pour le code existant. Il est plus facile d'identifier les cas extrêmes lorsque vous écrivez (ou êtes sur le point d'écrire) le code.
la source
Cette compréhension est fausse.
Les tests unitaires vérifient le comportement de l' unité testée .
En ce sens, une unité n'est pas nécessairement "une méthode dans une classe". J'aime la définition d'une unité de Roy Osherove dans The Art of Unit Testing :
Sur cette base, un test unitaire doit vérifier chaque comportement souhaité de votre code. Où le "désir" est plus ou moins pris des exigences.
Il a raison, mais d'une manière différente de celle qu'il pense.
D'après votre question, je comprends que vous êtes le "testeur spécialisé" de ce projet.
Le grand malentendu est qu'il s'attend à ce que vous écriviez des tests unitaires (par opposition à "test utilisant un framework de tests unitaires"). L’écriture des tests ynit relève de la responsabilité des développeurs et non des testeurs (dans un monde idéal, je sais ...). D'autre part, vous avez balisé cette question avec TDD, ce qui implique exactement cela.
En tant que testeur, votre travail consiste à écrire (ou exécuter manuellement) des tests de modules et / ou d'applications. Et ce type de tests devrait principalement vérifier que toutes les unités fonctionnent ensemble sans à-coups. Cela signifie que vous devez sélectionner vos scénarios de test afin que chaque unité soit exécutée au moins une fois . Et cette vérification est qui fonctionne. Le résultat réel est moins important car il peut être modifié en fonction des besoins futurs.
Pour souligner encore une fois l’analogie de l’automobile: combien d’essais sont effectués avec une voiture au bout de la chaîne de montage? Exactement un: il doit conduire au parking par lui-même ...
Le point ici est:
Nous devons être conscients de cette différence entre les "tests unitaires" et les "tests automatisés utilisant un cadre de tests unitaires".
Les tests unitaires sont un filet de sécurité. Ils vous donnent la confiance nécessaire pour refactoriser votre code afin de réduire la dette technique ou pour ajouter un nouveau comportement sans craindre de rompre le comportement déjà mis en œuvre.
Vous n'avez pas besoin d'une couverture de code à 100%.
Mais vous avez besoin d'une couverture comportementale à 100%. (Oui, la couverture de code et la couverture de comportement sont en quelque sorte corrélées, mais elles ne sont pas identiques pour le plaisir.)
Si votre couverture de comportement est inférieure à 100%, une exécution réussie de votre suite de tests ne signifie rien, car vous auriez pu modifier certains comportements non testés. Et vous serez remarqué par votre client le lendemain de la mise en ligne de votre publication ...
Conclusion
Peu de tests valent mieux qu’aucun test. Sans aucun doute!
Mais il n’existe pas de trop nombreux tests unitaires.
En effet, chaque test unitaire vérifie une attente unique concernant le comportement des codes . Et vous ne pouvez pas écrire plus de tests unitaires que ce que vous attendez de votre code. Et un trou dans votre harnais de sécurité est une chance pour un changement indésirable de nuire au système de production.
la source
Absolument oui. J'étais SDET pour une grande entreprise de logiciels. Notre petite équipe devait maintenir un code de test qui était géré par une équipe beaucoup plus grande. En plus de cela, notre produit avait des dépendances qui introduisaient constamment des changements brusques, ce qui impliquait une maintenance de test constante pour nous. Nous n'avions pas l'option d'augmenter la taille de l'équipe. Nous avons donc dû abandonner des milliers de tests moins utiles en cas d'échec. Sinon, nous ne serions jamais en mesure de suivre les défauts.
Avant de considérer ce problème comme un simple problème de gestion, considérez que de nombreux projets dans le monde réel souffrent d'une réduction des effectifs à l'approche du statut d'héritage. Parfois, cela commence même juste après la première publication.
la source
Avoir plus de lignes de code de test que de code de produit n'est pas nécessairement un problème, en supposant que vous refacturiez votre code de test pour éliminer le copier-coller.
Le problème, c’est que les tests soient des miroirs de votre implémentation, sans signification commerciale. Par exemple, des tests chargés de faux et de bouts et affirmant uniquement qu’une méthode appelle une autre méthode.
Une excellente citation dans le document "Pourquoi la plupart des tests unitaires sont-ils des déchets" est que les tests unitaires devraient avoir un "large, formel, indépendant oracle de la justesse, et ... une valeur commerciale imputable"
la source
Une chose que je n'ai pas vue mentionnée est que vos tests doivent être rapides et faciles à exécuter par n'importe quel développeur, à tout moment.
Vous ne voulez pas avoir à vérifier dans le contrôle de code source et à attendre une heure ou plus (selon la taille de votre base de code) avant la fin des tests pour voir si votre modification a cassé quelque chose - vous voulez pouvoir le faire sur votre propre ordinateur avant de vous enregistrer dans le contrôle de source (ou au moins, avant d’appliquer vos modifications). Idéalement, vous devriez pouvoir exécuter vos tests avec un seul script ou une seule pression sur un bouton.
Et lorsque vous exécutez ces tests localement, vous souhaitez qu'ils s'exécutent rapidement, de l'ordre de quelques secondes. Plus lentement, et vous serez tenté de ne pas les exécuter assez ou pas du tout.
Donc, avoir autant de tests que de les exécuter en quelques minutes, ou avoir quelques tests trop complexes, peut poser problème.
la source