Existe-t-il trop de tests unitaires?

139

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.

utilisateur2954463
la source
145
Ça dépend. Est-ce que vous écrivez un jeu de tic-tac-toe, ou écrivez-vous du code pour gérer un réacteur nucléaire?
Bryan Oakley
11
Avec suffisamment de tests unitaires, vous pouvez détecter des problèmes d’implémentation matérielle exotiques, tels que le bogue Pentium FDIV ou des corrélations dans les primitives cryptographiques. Juste une limite pratique sur quand c'est trop coûteux.
Nat
5
Les tests à des niveaux plus élevés vous fourniront une meilleure perspective de la couverture réelle. Par couverture réelle, j'entends celui qui est le plus susceptible de se produire lors de l'utilisation régulière du système. C'est le genre de couverture que vous souhaitez obtenir en premier. YAGNI ou code mort qui, une fois supprimé, contribuerait également à augmenter la couverture globale des 50% restants.
Laiv
5
Si vous obtenez trop de tests (que vous ne semblez pas avoir pour le moment), le problème le plus probable est que le code que vous testez en fait trop. Donc, la responsabilité unique n'est pas respectée. Lorsque le code est bien divisé, les tests ne créeront pas non plus beaucoup de charge. Si les classes font beaucoup, ont beaucoup d'effets secondaires, etc. cela deviendra un cauchemar.
Luc Franken
12
Le document de test de sqlite est une lecture amusante: sqlite.org/testing.html . Citation: "La bibliothèque SQLite est composée d’environ 122,9 KSLOC de code C. Par comparaison, le projet contient 745 fois plus de code de test et de scripts de test - 91596.1 KSLOC."
user60561

Réponses:

180

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.

Karl Bielefeldt
la source
11
Merci pour votre réponse. Cela a aidé à mettre ma question en perspective et à aborder le vrai problème - mon attitude! +1
user2954463
43
@astra Votre attitude n'est pas si mauvaise. C'est bien de se demander pourquoi. Pour répondre à votre autre question, excellente question: "Comment connaître mes pairs et que je partage la même idée concernant les" cas d'utilisation les plus courants "? Vous leur demandez de regarder vos tests. Regardez les leurs. Parlez-en. Vous apprendrez La révision du code est rarement une perte de temps, bien que j'aie tendance à faire le mien dans un terminal plutôt que dans une salle de conférence
candied_orange
18
Un test qui n'échoue jamais dans 10 ans ne garantit même pas que ce soit inutile, il pourrait échouer en
11ème
24
De manière pragmatique, vous pourriez adopter l'approche inverse. Ecrivez les tests qui, selon vous, couvrent les cas courants. Mais ensuite, chaque fois que vous rencontrez un échec, écrivez un test pour couvrir cette zone.
Stannius
10
@Pharap Mon seul problème avec cette réponse est qu'il existe l'hypothèse implicite qu'un test ne peut ajouter de la valeur qu'en cas d'échec. Un bon test unitaire fournit également une excellente forme de documentation vivante. Cela a également ajouté de la valeur lorsque vous avez rédigé le test, en vous obligeant à penser à la réutilisabilité / la composabilité / à l’encapsulation. D'après mon expérience, le code non testé a tendance à être des animaux monolithiques inflexibles.
ArTs
66

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é.

Frank Hileman
la source
16
Les votants qui soulignent les conditions préalables, postérieures et les invariants doivent être traités comme des tests unitaires. De cette manière, chaque utilisation est un test unitaire lorsque ce code de débogage est actif.
Persixty
C'est une excellente réponse et correspond parfaitement à mon expérience.
Tony Ennis
Et un autre problème: si vous avez des chèques bloqués (vous devriez vraiment!) Avoir de grandes quantités de qualité médiocre, même de longs tests en cours ralentiront le processus sans fournir de réels avantages. Et puis évidemment le fait amusant de changer une chose dans une classe et que des centaines de tests échouent.
Voo
3
C'est une bien meilleure réponse que celle acceptée! "Dans certains cas, l'essentiel de l'effort de développement consiste à mettre à jour des tests de qualité médiocre". Plus que ne pas avoir de test du tout, à certains égards.
Benjamin Hodgson
36

Réponses à vos questions

Existe-t-il trop de tests unitaires?

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.

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.

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.

Ce ratio va-t-il devenir ingérable si nous augmentons la couverture de notre code?

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.

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.

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).

Cependant, dans la demande d'attraction, mon responsable technique a indiqué que je devrais me concentrer sur des tests de niveau supérieur.

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.

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.

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.

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.

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.

AnoE
la source
1
Les tests d'intégration sont excellents mais ne remplacent pas les tests unitaires, pas plus que les applications métiers. Ils posent de nombreux problèmes: a) par définition, leur exécution est longue (cela signifie également que les tests incrémentiels sont pratiquement inutiles), b) ils rendent extrêmement difficile l'identification précise du problème réel (oh 50 tests d'intégration ont échoué, quel changement a causé cela?) et c) ils couvrent les mêmes chemins de code à plusieurs reprises.
Voo
1
a) est un problème car cela rend fastidieuse l'exécution des tests sur les check-ins bloqués et rend moins probable le fait que les programmeurs exécutent les tests de manière répétée pendant le développement, ce qui, associé à b), diminue l'efficacité et la capacité à diagnostiquer rapidement les bogues. c) signifie que changer une petite chose peut facilement faire échouer des dizaines ou des centaines (déjà passés) de vos tests d'intégration, ce qui signifie que vous passerez un sac de temps à les réparer. Cela signifie également que les tests d'intégration ne testent généralement que des chemins heureux, car l'écriture de ces tests ciblés est fastidieuse, voire impossible.
Voo
1
@Voo, tout ce que vous avez écrit est vrai, et autant que je sache, j'ai déjà mentionné tous les problèmes que vous avez notés dans la réponse ...
AnoE
Si vous êtes d'accord avec ce résumé, je ne vois vraiment pas comment vous pouvez en venir à la conclusion que vous préféreriez les tests d'intégration aux tests unitaires. Des suites complètes de tests d'intégration de grands programmes prennent des heures, voire des jours, à exécuter. Elles sont excellentes, mais sont pratiquement inutiles lors du développement. Et vos tests d'acceptation (que tout le monde fait, n'est-ce pas?) Détecteront bon nombre des mêmes problèmes que les tests d'intégration trouveraient qui seraient oubliés par les tests unitaires - l'inverse n'est toutefois pas vrai.
Voo
24

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:

  1. 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.

  2. 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.

  3. 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.

Bruno Guardia
la source
2
Ce n’est pas un problème avec les tests unitaires, mais avec l’organisation qui a mal défini ses priorités en demandant un numéro spécifique pour la couverture des tests unitaires sans dépenser les ressources nécessaires à la création et à l’exécution de tests appropriés à d’autres niveaux.
Jwenting
2
Entendez-vous fermement sur le n ° 3 et l'étendriez également à la transmission manuelle d'instances de classes de niveau inférieur à des classes de niveau supérieur. Si une chose de haut niveau s'appuie sur une chose de bas niveau pour faire quelque chose, c'est pas grave. Si les appelants en dissimulent les détails, j'appellerais ce bon design. Mais si vous intégrez ensuite les objets de bas niveau dans l'interface de objets de haut niveau et faites en sorte que les appelants le transmettent, car cela rend vos tests plus jolis, maintenant la queue remue le chien. (Si des objets de bas niveau sont réutilisés dans de nombreux endroits et changent beaucoup, cela change des choses. Dans mon expérience, cela n'a pas été typique.)
johncip
J'adore votre description, @johncip. C'est certainement un exemple fréquent de la façon dont une belle classe devient horrible en ajoutant une série de paramètres inutiles au constructeur ...
Bruno Guardia
19

N'oubliez pas que chaque test a un coût et un avantage. Les inconvénients incluent:

  • un test doit être écrit;
  • un test prend (généralement très peu de temps) pour s'exécuter;
  • un test doit être maintenu avec le code - les tests doivent changer lorsque les API testées changent;
  • vous devrez peut-être modifier votre conception pour pouvoir écrire un test (bien que ces modifications soient généralement meilleures).

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.

Le secret de Salomonoff
la source
12

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

  • Une tranche de temps dans la suite de tests unitaires
  • Peut-être n’ajoute aucune valeur réelle, car il s’agit en fait d’une copie d’un autre test ayant une chance infime que d’autres tests réussissent et que ce test échoue.

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:

  1. 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».

  2. Testez toutes les valeurs ou tailles min / max pertinentes

  3. Testez tout ce que vous pensez être un cas spécial amusant, en particulier des valeurs "impaires".

  4. 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:

  1. Faire des tests en vrac de plus de cas.
  2. Comparer le résultat à une implémentation 'brute-force' et vérifier les invariants.
  3. Utilisation d'une méthode de production de tests aléatoires et vérification par rapport à la force brute et aux post-conditions, y compris les invariants.

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.

Persixty
la source
4
Je ne dirais pas que la couverture à 100% est pragmatique. La couverture à 100% est un niveau extrêmement élevé.
Bryan Oakley
Malheureusement, même la méthode aléatoire peut manquer des erreurs. Rien ne peut remplacer les preuves, même informelles.
Frank Hileman
@BryanOakley Point pris. C'est une exagération. Mais il est plus important de s'en approcher que de donner du crédit. "J'ai testé le chemin facile c'est tout bon" va toujours causer des problèmes plus tard.
Persixty
@FrankHileman La question n'était pas "Est-ce que le test unitaire est un bon substitut pour concevoir des logiciels avec soin, une logique de vérification statique et des algorithmes de vérification", alors la réponse est non. Aucune de ces méthodes ne produira à elle seule un logiciel de haute qualité.
Persixty
3

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.

Toby Speight
la source
3

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.

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 :

Une unité est tout le code de production qui a la même raison de changer.

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.


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 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".


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.

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.

Timothy Truckle
la source
2

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.

mrog
la source
4
"En plus de cela, notre produit avait des dépendances qui introduisaient constamment des changements brusques, ce qui impliquait un entretien constant des tests pour nous." - Les tests dont vous dites qu'ils nécessitent un entretien ressemblent à ceux qui ont de la valeur si vos dépendances se cassent constamment.
CodeMonkey
2
Ce n'est pas un problème avec les tests, mais avec l'organisation.
Jwenting
2
@CodeMonkey Les dépendances ne se sont pas cassées. Ils ont été mis à jour de manière à nécessiter des modifications de notre produit. Oui, les tests étaient utiles, mais pas aussi précieux que les autres. Les tests automatisés ont plus de valeur lorsque le test manuel équivalent est difficile.
Mrog
2
@jwenting Oui, c'est un problème d'organisation, pas un problème de code. Mais cela ne change pas le fait qu'il y a eu trop de tests. Un test qui échoue et qui ne peut faire l’objet d’une enquête est inutile, quelle que soit sa cause.
Mrog
Qu'est-ce qu'un "SDET"?
Peter Mortensen
1

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"

Wrschneider
la source
0

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.

mmathis
la source