30 votes

Une couverture de code à 100 % est-elle une bonne chose lorsque l'on effectue des tests unitaires ?

J'ai toujours appris que faire une couverture maximale du code avec des tests unitaires est bon . J'entends aussi des développeurs de grandes entreprises comme Microsoft dire qu'ils écrivent plus de lignes de code de test que le code exécutable lui-même.

Maintenant, Est-ce que c'est vraiment génial ? Ne semble-t-il pas parfois perte totale de temps qui n'a d'effet que sur rendre l'entretien plus difficile ?

Par exemple, disons que j'ai une méthode DisplayBooks() qui alimente une liste de livres à partir d'une base de données. Les exigences du produit indiquent que s'il y a plus de cent livres dans le magasin, seule une centaine doit être affichée .

Donc, avec TDD,

  1. Je vais commencer par faire un test unitaire BooksLimit() ce qui permettra de sauvegarder deux cents livres dans la base de données, appelez DisplayBooks() et faire un Assert.AreEqual(100, DisplayedBooks.Count) .
  2. Puis je testerai si ça échoue,
  3. Alors je vais changer DisplayBooks() en fixant la limite des résultats à 100, et
  4. Enfin, je vais réexécuter le test pour voir s'il réussit.

Eh bien, n'est-il pas plus facile de passer directement à la troisième étape, et de ne jamais faire BooksLimit() de test unitaire ? Et n'est-il pas plus agile, lorsque les exigences passent de 100 à 200 livres, de ne changer qu'un seul caractère, au lieu de changer les tests, de les exécuter pour vérifier s'ils échouent, de changer le code et d'exécuter à nouveau les tests pour vérifier s'ils réussissent ?

Note : supposons que le code est entièrement documenté. Sinon, certains pourraient dire, et ils auraient raison, que faire des tests unitaires complets aidera à comprendre un code qui manque de documentation. En fait, avoir un BooksLimit() Le test unitaire montrera très clairement qu'il existe un nombre maximum de livres à afficher, et que ce nombre maximum est de 100. Il serait beaucoup plus difficile de pénétrer dans le code qui ne fait pas partie des tests unitaires, car cette limite peut être mise en œuvre par le biais de la méthode suivante for (int bookIndex = 0; bookIndex < 100; ... o foreach ... if (count >= 100) break; .

34voto

Stephen Points 16714

N'est-il pas plus simple de passer directement à la troisième étape, et de ne jamais faire de test unitaire de BooksLimit() ?

Oui... Si vous ne passez pas de temps à écrire des tests, vous passerez moins de temps à écrire des tests. Votre projet pourrait prendre plus longtemps globalement, parce que vous passerez beaucoup de temps à déboguer, mais peut-être est-ce plus facile à expliquer à votre responsable ? Si c'est le cas... trouvez un nouvel emploi ! Les tests sont essentiels pour améliorer votre confiance dans votre logiciel.

L'unittesting donne le plus de valeur lorsque vous avez beaucoup de code. Il est facile de déboguer un simple devoir à la maison en utilisant quelques classes sans unittesting. Une fois que vous serez dans le monde, et que vous travaillerez sur des bases de code de plusieurs millions de lignes, vous en aurez besoin. Vous ne pouvez pas tout simplement passez votre débogueur à travers tout. Vous ne pouvez tout simplement pas tout comprendre. Vous avez besoin de savoir que les classes dont vous dépendez fonctionnent. Vous devez savoir si quelqu'un dit "Je vais juste faire ce changement au comportement... parce que j'en ai besoin", mais il a oublié qu'il y a deux cents autres utilisations qui dépendent de ce comportement. Unittesting aide à prévenir cela.

Pour ce qui est de rendre la maintenance plus difficile : PAS QUESTION ! Je ne peux pas assez insister sur ce point.

Si vous êtes la seule personne à avoir travaillé sur votre projet, alors oui, vous pouvez penser cela. Mais ce sont des paroles en l'air ! Essayez de vous mettre à niveau sur un projet de 30k lignes sans unittests. Essayez d'ajouter des fonctionnalités qui nécessitent des changements significatifs au code sans unittests. Il y a pas de confiance que vous ne brisez pas les hypothèses implicites faites par les autres ingénieurs. Pour un mainteneur (ou un nouveau développeur sur un projet existant) les unittests sont essentiels. Je me suis appuyé sur les unittests pour la documentation, pour le comportement, pour les hypothèses, pour me dire quand j'ai cassé quelque chose (que je pensais sans rapport). Parfois, une API mal écrite a des tests mal écrits et peut être un cauchemar à changer, parce que les tests prennent tout votre temps. Un jour ou l'autre, vous voudrez remanier ce code et le corriger, mais vos utilisateurs vous en seront reconnaissants : votre API sera beaucoup plus facile à utiliser grâce à cela.

Une note sur la couverture :

Pour moi, il ne s'agit pas d'une couverture de test à 100%. Une couverture à 100% ne trouve pas tous les bogues, considérez une fonction avec deux if déclarations :

// Will return a number less than or equal to 3
int Bar(bool cond1, bool cond2) {
  int b;
  if (cond1) {
    b++;
  } else {
    b+=2;
  }

  if (cond2) {
    b+=2;
  } else {
    b++;
  }
}

Considérons maintenant que j'écrive un test qui teste :

EXPECT_EQ(3, Bar(true, true));
EXPECT_EQ(3, Bar(false, false));

C'est une couverture à 100%. C'est aussi une fonction qui ne respecte pas le contrat - Bar(false, true); échoue, car il renvoie 4. La "couverture complète" n'est donc pas l'objectif final.

Honnêtement, je sauterais des tests pour BooksLimit() . Il retourne une constante, donc cela ne vaut probablement pas la peine de les écrire (et il devrait être testé lors de l'écriture de DisplayBooks() ). Je serai peut-être triste lorsque quelqu'un décidera de calculer (incorrectement) cette limite à partir de la taille de l'étagère, et que celle-ci ne répondra plus à nos exigences. J'ai déjà été échaudé par l'expression "ne vaut pas la peine d'être testé". L'année dernière, j'ai écrit du code et j'ai dit à mon collègue : "Cette classe est principalement constituée de données, elle n'a pas besoin d'être testée". Elle avait une méthode. Il y avait un bug. Il a été mis en production. Il nous a bipé au milieu de la nuit. Je me sentais stupide. Alors j'ai écrit les tests. Et puis j'ai longuement réfléchi à ce qui constitue un code "ne valant pas la peine d'être testé". Il n'y en a pas beaucoup.

Donc, oui, vous pouvez sauter certains tests. Une couverture de test à 100 %, c'est bien, mais cela ne signifie pas par magie que votre logiciel est parfait. Tout se résume à la confiance face au changement.

Si je mets class A , class B y class C ensemble, et que je trouve quelque chose qui ne fonctionne pas, est-ce que je veux passer du temps à déboguer les trois ? Non. Je veux savoir que A y B ont déjà rempli leurs contrats (via les unittests) et mon nouveau code en class C est probablement cassé. Donc je le déteste. Comment puis-je savoir qu'il est cassé, si je ne le déteste pas ? En cliquant sur quelques boutons et en essayant le nouveau code ? C'est bien, mais pas suffisant. Une fois que votre programme passe à l'échelle, il sera impossible de réexécuter tous vos tests manuels pour vérifier que tout fonctionne correctement. C'est pourquoi les personnes qui utilisent unittest automatisent généralement aussi l'exécution de leurs tests. Dites-moi "Pass" ou "Fail", ne me dites pas "la sortie est ...".

OK, je vais aller écrire d'autres tests...

11voto

soru Points 2960

Une couverture de test unitaire à 100% est généralement une odeur de code, un signe que quelqu'un est devenu obsédé par la barre verte de l'outil de couverture, au lieu de faire quelque chose de plus utile.

Quelque part autour de 85% est le point idéal, où un test qui échoue indique plus souvent qu'autrement un problème réel ou potentiel, plutôt que d'être simplement une conséquence inévitable de tout changement textuel qui n'est pas à l'intérieur des marqueurs de commentaires. Vous ne documentez pas d'hypothèses utiles sur le code si vos hypothèses sont "le code est ce qu'il est, et s'il était différent, il serait autre chose". C'est un problème résolu par un outil de checksum sensible aux commentaires, pas un test unitaire.

J'aimerais qu'il existe un outil qui vous permette de spécifier la couverture cible. Et ensuite, si vous le dépassez accidentellement, montrer des choses en jaune/orange/rouge pour vous pousser à supprimer certains des tests supplémentaires inutiles.

6voto

Lucero Points 38928

Si l'on considère un problème isolé, vous avez tout à fait raison. Mais les tests unitaires servent à couvrir toutes les intentions que vous avez pour un certain morceau de code.

Fondamentalement, les tests unitaires formulent vos intentions. Avec un nombre croissant d'intentions, le comportement du code à tester peut toujours être vérifié par rapport à toutes les intentions formulées jusqu'à présent. Chaque fois qu'un changement est effectué, vous pouvez prouver qu'il n'y a pas d'effet secondaire qui brise les intentions existantes. Les bogues nouvellement découverts ne sont rien d'autre qu'une intention (implicite) qui n'est pas tenue par le code, de sorte que vous formulez votre intention comme un nouveau test (qui échoue au début) et le corrigez.

Pour un code unique, les tests unitaires ne valent en effet pas la peine d'être effectués car aucun changement majeur n'est attendu. Cependant, pour tout bloc de code qui doit être maintenu ou qui sert de composant pour d'autres codes, garantir que toutes les intentions sont tenues pour toute nouvelle version vaut beaucoup (en termes de moins d'efforts pour essayer manuellement de vérifier les effets secondaires).

Le point de basculement où les tests unitaires vous font réellement gagner du temps et donc de l'argent dépend de la complexité du code, mais il existe toujours un point de basculement qui est généralement atteint après seulement quelques itérations de changements. Enfin, et ce n'est pas le moins important, ils vous permettent d'envoyer des corrections et des modifications beaucoup plus rapidement sans compromettre la qualité de votre produit.

5voto

Lukasz Dziedzia Points 4134

Il n'y a pas de relation explicite entre la couverture du code et un bon logiciel. Vous pouvez facilement imaginer un morceau de code qui a une couverture de code de 100% (ou presque) et qui contient encore beaucoup de bogues. (Ce qui ne signifie pas que les tests sont mauvais !)

Votre question sur l'agilité de l'approche "pas de test du tout" n'est bonne que dans une perspective à court terme (ce qui signifie qu'elle n'est probablement pas bonne si vous prévoyez de construire votre programme à plus long terme). Je sais par expérience que des tests aussi simples sont très utiles lorsque votre projet prend de l'ampleur et qu'à un moment donné, vous devez apporter des modifications importantes. Cela peut être un moment où vous vous direz "C'était une bonne décision de passer quelques minutes supplémentaires pour écrire ce minuscule test qui a repéré le bug que je viens d'introduire !". .

J'étais récemment un grand fan de la couverture de code, mais elle s'est transformée (heureusement) en quelque chose du genre Couverture des problèmes approche. Cela signifie que vos tests doivent couvrir tous les problèmes et les bogues qui ont été repérés, et non seulement lignes de code . Il n'est pas nécessaire de faire un course à la couverture du code .

Je comprends le mot "Agile" en termes d'essais numériques comme suit le nombre de tests qui m'aide à construire de bons logiciels et à ne pas perdre de temps à écrire des morceaux de code inutiles". plutôt que "couverture à 100 %" ou "pas de tests du tout". C'est très subjectif et cela dépend de votre expérience, de votre équipe, de la technologie et de nombreux autres facteurs.

L'effet secondaire psychologique de la "couverture de code à 100%" est que vous pouvez penser que votre code n'a pas de bogues, ce qui n'est jamais vrai :)

2voto

fishtoprecords Points 835

Je suis d'accord avec @soru, une couverture de test à 100% n'est pas un objectif rationnel.

Je ne crois pas qu'il existe un outil ou une métrique qui puisse vous indiquer le "bon" montant de couverture. Lorsque j'étais à l'université, le travail de mon directeur de thèse consistait à concevoir la couverture des tests pour le code "muté". Il prenait une suite de tests, puis exécutait un programme automatisé pour faire des erreurs dans le code source testé. L'idée était que le code muté contenait des erreurs que l'on trouverait dans le monde réel, et donc que la suite de tests qui trouvait le plus grand pourcentage de code cassé était gagnante.

Bien que sa thèse ait été acceptée, et qu'il soit maintenant professeur dans une grande école d'ingénieurs, il n'a pas trouvé non plus :

1) un nombre magique de couverture de test qui est optimal 2) une suite qui pourrait trouver 100% des erreurs.

Notez que l'objectif est de trouver 100% des erreurs, et non de trouver 100% de couverture.

Que les 85% de @soru soient justes ou non est un sujet de discussion. Je n'ai aucun moyen d'évaluer si un meilleur chiffre serait 80% ou 90% ou autre chose. Mais en tant qu'évaluation de travail, 85% me semble correct.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X