Vos tests unitaires constituent-ils une couverture de code à 100%? Oui ou non, et pourquoi ou pourquoi pas.
Non pour plusieurs raisons:
public static String foo(boolean someCondition) {
String bar = null;
if (someCondition) {
bar = "blabla";
}
return bar.trim();
}
et le test unitaire:
assertEquals("blabla", foo(true));
Le test réussira et la couverture de votre code est de 100%. Cependant, si vous ajoutez un autre test:
assertEquals("blabla", foo(false));
alors vous obtiendrez une NullPointerException
. Et comme vous étiez à 100% avec le premier test, vous n’auriez pas nécessairement écrire le second!
En général, j'estime que le code critique doit être couvert à près de 100%, tandis que l'autre code peut l'être à 85-90%.
À tous les testeurs de couverture à 90%:
Le problème, c'est que le code difficile à tester de 10% est également le code non trivial qui contient 90% du bogue! C’est la conclusion que j’ai tirée empiriquement après de nombreuses années de TDD.
Et après tout, c'est une conclusion assez simple. Ce code de 10% difficile à tester, est difficile à tester car il reflète un problème commercial délicat ou un défaut de conception délicat, ou les deux. Ces raisons exactes conduisent souvent au code buggy.
Mais aussi:
Non, car il existe un compromis pratique entre des tests unitaires parfaits et la fin d'un projet :)
Il est rarement pratique d'obtenir une couverture de code à 100% dans un système non trivial. La plupart des développeurs qui écrivent des tests unitaires tournent pour le milieu des années 90.
Un outil de test automatisé tel que Pex peut aider à augmenter la couverture de code. Cela fonctionne en recherchant les cas difficiles à trouver dans Edge.
Oui.
Cela dépend de la langue et du cadre que vous utilisez pour déterminer si cela est facile à réaliser.
Nous utilisons Ruby on Rails pour mon projet actuel. Ruby est très "mockable" en ce que vous pouvez écraser de gros morceaux de votre code sans avoir à créer des compositions de classe trop complexes et des conceptions de construction que vous auriez à faire dans d'autres langues.
Cela dit, nous n’avons qu’une couverture de 100% line (en gros ce que rcov vous donne). Vous devez toujours penser à tester toutes les branches requises.
Cela n’est vraiment possible que si vous l’incluez dès le départ dans votre construction d’intégration continue et que casse la construction} si la couverture passe au-dessous de 100%, ce qui incite les développeurs à le corriger immédiatement. Bien sûr, vous pouvez choisir un autre chiffre comme cible, mais si vous débutez à zéro, il n'y a pas beaucoup de différence pour que l'effort passe de 90% à 100%
Nous avons également toute une série d'autres métriques qui cassent la construction si elles dépassent également un seuil donné (complexité cyclomatique, duplication par exemple), toutes ces choses vont ensemble et contribuent à se renforcer mutuellement.
Encore une fois, vous devez vraiment avoir ces éléments en place dès le début pour continuer à travailler à un niveau strict - ou bien définir une cible que vous pouvez atteindre, et augmentez-le progressivement jusqu'à atteindre un niveau qui vous convient.
Est-ce que cela ajoute de la valeur? J'étais sceptique au début, mais je peux honnêtement dire que oui. Pas principalement parce que vous avez minutieusement testé le code (bien que ce soit définitivement un avantage), mais plus en termes d'écriture de code simple, facile à tester et à raisonner. Si vous savez que vous devez avoir une couverture de test à 100%, vous arrêtez d'écrire de manière trop complexe si/else/tandis que/essayez/attrapez des monstruosités et restez simple, stupide.
Ce que je fais quand j'ai la chance, c'est d'insérer des déclarations sur chaque branche du code pouvant être récupéré et cet enregistrement s'ils ont été touchés, afin que je puisse faire une sorte de comparaison pour voir quelles déclarations n'ont pas été touchées. . C'est une corvée, donc je ne suis pas toujours doué pour ça.
Je viens de construire une petite application d'interface utilisateur à utiliser dans les enchères caritatives, qui utilise MySQL comme base de données. Puisque je ne voulais vraiment pas vraiment que cela se produise au milieu d’une enchère, j’ai essayé quelque chose de nouveau.
Comme c'était dans VC6 (C++ + MFC), j'ai défini deux macros:
#define TCOV ASSERT(FALSE)
#define _COV ASSERT(TRUE)
et puis j'ai saupoudré
TCOV;
tout au long du code, sur chaque chemin distinct que je pouvais trouver, et dans chaque routine. Ensuite, j'ai exécuté le programme sous le débogueur, et chaque fois qu'il rencontrait un TCOV
, il s'arrêtait. Recherchez dans le code les problèmes évidents, puis modifiez-le en _COV
, puis continuez. Le code se recompilerait à la volée et passerait à la prochaine TCOV
. De cette façon, j'éliminais lentement, péniblement, suffisamment de déclarations TCOV
pour qu'il soit exécuté "normalement".
Au bout d’un moment, j’ai saisi le code pour TCOV
, ce qui indiquait le code que je n’avais pas testé. Ensuite, je suis retourné et l'ai relancé, en m'assurant de tester plus de branches que je n'avais pas essayées auparavant. Je l'ai fait jusqu'à ce qu'il ne reste plus aucune instruction TCOV
dans le code.
Cela a pris quelques heures, mais dans le processus, j'ai trouvé et corrigé plusieurs bugs. Il n’ya aucun moyen pour que j’ai eu la discipline de faire et de suivre un plan de test qui aurait été aussi minutieux. Non seulement je savais que j’avais couvert toutes les branches, mais c’était ce qui m’a fait regarder chaque branche en cours d’exécution - un très bon type d’examen du code.
Ainsi, que vous utilisiez ou non un outil de couverture, il s'agit d'un bon moyen de supprimer les bogues qui autrement se dissimuleraient dans le code jusqu'à un moment plus embarrassant.
Personnellement, je trouve que la couverture de 100% des tests est problématique sur plusieurs niveaux. Avant tout, vous devez vous assurer que vous obtenez un avantage tangible et économique en tirant parti des tests unitaires que vous écrivez. De plus, les tests unitaires, comme tout autre code, sont codés. Cela signifie que, comme tout autre code, il faut en vérifier l'exactitude et en assurer la maintenance. Ce délai supplémentaire, qui consiste à vérifier l'exactitude du code supplémentaire, à le maintenir et à maintenir les tests valides en réponse à des modifications du code commercial, augmente les coûts. Obtenir une couverture de test à 100% et vous assurer de tester votre code aussi minutieusement que possible est un effort louable, mais le faire à tout prix ... eh bien, est souvent trop coûteux.
Il arrive souvent que des vérifications d'erreurs et de validité couvrent des vérifications d'erreurs ou de validité extrêmement rares, mais qu'il est tout à fait possible, les cas exceptionnels sont un exemple de code qu'il n'est pas nécessaire de couvrir. Le montant de temps , effort (et finalement argent ) qui doit être investi pour obtenir la couverture de si rares cas marginaux est souvent gaspillage à la lumière des autres besoins de l'entreprise. Les propriétés font souvent partie du code qui, en particulier avec C # 3.0, n'a pas besoin d'être testée, car la plupart sinon toutes les propriétés se comportent exactement de la même manière et sont excessivement simples (retour ou jeu d'instructions uniques). Il serait probablement plus judicieux d’investir ailleurs dans des tests unitaires autour de milliers d’immeubles où un retour sur investissement plus important et plus précieux pourrait être réalisé.
Au-delà d’une simple couverture de test à 100%, il existe des problèmes similaires lorsqu’on essaie de configurer l’unité «parfaite». Les frameworks moqueurs ont progressé à un degré incroyable ces derniers temps, et presque tout peut être ridiculisé (si vous êtes prêt à payer de l'argent, TypeMock peut se moquer de tout et de rien, mais cela coûte cher.) Cependant, il arrive souvent que les dépendances de votre code n’ont pas été écrits de manière fictive (il s’agit en fait d’un problème fondamental lié à la grande majorité du framework .NET lui-même.) Il est utile d’investir du temps pour obtenir la portée adéquate d’un test. Le temps de se moquer de tout et de n'importe quoi sous la surface du Soleil, en ajoutant des couches d'abstraction et des interfaces pour le rendre possible, est encore une fois une perte de temps, d'efforts et, en définitive, d'argent.
Le but ultime des tests ne devrait pas être d'atteindre la couverture ultime du code. L’objectif ultime devrait être d’obtenir la plus grande valeur par unité de temps investie dans la rédaction de tests unitaires, tout en couvrant autant que possible au cours de cette période. La meilleure façon d’y parvenir est d’adopter l’approche BDD: spécifiez vos préoccupations, définissez votre contexte et vérifiez que les résultats attendus se produisent pour tout élément de comportement en cours de développement (comportement ... non unité.)
Sur un nouveau projet, je pratique le TDD et maintiens une couverture en ligne de 100%. Il se produit principalement naturellement par TDD. Les lacunes de la couverture valent généralement l'attention et sont faciles à combler. Si l'outil de couverture que j'utilise fournit une couverture de branche ou autre chose, je ferais attention à cela, bien que je n'ai jamais vu de couverture de branche me dire quoi que ce soit, probablement parce que TDD est arrivé en premier.
Mon principal argument en faveur du maintien d'une couverture de 100% (si vous vous souciez de la couverture) est que il est beaucoup plus facile de maintenir une couverture de 100% que de gérer une couverture inférieure à 100% . Si vous avez une couverture de 100% et que celle-ci tombe, vous savez immédiatement pourquoi et pouvez la réparer facilement, car la baisse est dans le code sur lequel vous venez de travailler. Mais si vous vous contentez de 95% ou plus, il est facile de rater les régressions de couverture et vous revenez toujours sur les lacunes connues. C'est la raison exacte pour laquelle les meilleures pratiques actuelles exigent que la suite de tests réussisse complètement. Rien de moins est plus difficile, pas plus facile à gérer.
Mon attitude est définitivement renforcée par le fait que je travaille dans Ruby depuis un certain temps, où il existe d’excellents cadres de test et où les doubles tests sont faciles. Une couverture à 100% est également facile en Python. Je pourrais devoir abaisser mes normes dans un environnement avec des outils moins pratiques.
J'aimerais avoir les mêmes normes pour les projets hérités, mais je n'ai jamais trouvé pratique d'apporter une application volumineuse avec une couverture médiocre allant jusqu'à 100%; J'ai dû me contenter de 95-99%. Cela a toujours été trop de travail de revenir en arrière et de couvrir tout l'ancien code. Cela ne contredit pas mon argument selon lequel il est facile de garder une base de code à 100%; c'est beaucoup plus facile lorsque vous maintenez cette norme depuis le début.
Non, car j'ai passé mon temps à ajouter de nouvelles fonctionnalités qui aident les utilisateurs plutôt que de compliquer la tâche pour écrire des tests obscurs qui offrent peu de valeur. Je dis à l'unité de tester les grandes choses, les choses subtiles et les choses qui sont fragiles.
J'écris généralement des tests unitaires comme méthode de prévention de la régression. Lorsqu'un bogue que je dois corriger est signalé, je crée un test unitaire pour s'assurer qu'il ne refera pas surface à l'avenir. Je peux créer quelques tests pour les sections de fonctionnalité que je dois m'assurer de rester intact (ou pour les interactions complexes entre les parties), mais je souhaite généralement que la correction de bogue me dise qu’une est nécessaire.
Je réussis habituellement à atteindre 93 .. 100% avec ma couverture mais je ne vise plus à 100%. J'avais l'habitude de le faire et bien que ce soit faisable, l'effort au-delà d'un certain point ne vaut pas la peine, car des tests aveuglément évidents ne sont généralement pas nécessaires. Un bon exemple de ceci pourrait être la vraie branche d’évaluation du code suivant
public void method(boolean someBoolean) {
if (someBoolean) {
return;
} else {
/* do lots of stuff */
}
}
Toutefois, ce qui est important à atteindre, c’est de couvrir le plus possible les parties de la classe sur fonctionnelles , car ce sont les eaux dangereuses de votre application, le brouillard embrouillé des insectes rampants et le comportement indéfini et bien sûr la cirque aux puces qui rapporte de l’argent.
De Blog de Ted Neward .
À ce stade, la plupart des développeurs ont au moins entendu parler de l'adoption du meme Masochistic Testing, voire même de son adoption. Les collègues des NFJS, Stuart Halloway et Justin Gehtland, ont fondé un cabinet de conseil, Relevance, qui place la barre très haut en tant que norme culturelle d'entreprise: une couverture à 100% de leur code.
Neal Ford a rapporté que ThoughtWorks faisait des déclarations similaires, bien que je sache que les clients dressent parfois des obstacles accidentels dans leur manière d'atteindre cet objectif. C'est ambitieux, mais comme dit l'ancien proverbe américain
Si vous dirigez votre flèche vers le soleil, elle volera plus haut et plus loin que si vous la visiez au sol.
Oui, j'ai eu des projets qui ont eu une couverture en ligne de 100%. Voir ma réponse à un question.
Vous pouvez bénéficiez d’une couverture de 100% ligne, mais comme d’autres l’ont souligné ici sur SO et ailleurs sur Internet, c’est peut-être un minimum. Lorsque vous considérez la couverture des chemins et des branches, il reste encore beaucoup à faire.
L’autre façon de voir les choses est d’essayer de rendre votre code si simple qu’il est facile d’obtenir une couverture linéaire à 100%.
Je ne suis couvert à 100% que sur les nouveaux éléments de code écrits avec testability in mind. Avec une encapsulation correcte, chaque classe et fonction peut avoir des tests fonctionnels / qui donnent simultanément une couverture proche de 100%. Il suffit ensuite d'ajouter des tests supplémentaires couvrant certains cas Edge pour atteindre 100%.
Vous ne devriez pas écrire de tests uniquement pour obtenir une couverture. Vous devriez être en train d'écrire des tests fonctionnels pour vérifier l'exactitude/la conformité. Par une bonne spécification fonctionnelle qui couvre tous les motifs et une bonne conception logicielle, vous pouvez obtenir une bonne couverture gratuitement.
Dans de nombreux cas, il ne vaut pas la peine d’obtenir une couverture de 100% des relevés, mais dans certains cas, il vaut en vaut la peine. Dans certains cas, une couverture de 100% des relevés est bien trop lax une exigence.
La question clé à poser est la suivante: "quel est l'impact si le logiciel échoue (produit un résultat erroné)?". Dans la plupart des cas, l'impact d'un bug est relativement faible. Par exemple, vous devrez peut-être réparer le code dans quelques jours et réexécuter quelque chose. Toutefois, si l'impact est "une personne peut mourir en 120 secondes", l'impact est énorme et vous devez bénéficier d'une couverture de test lot supérieure à celle d'un relevé à 100%.
Je dirige le badge des meilleures pratiques de l’infrastructure de base pour Linux Foundation. Nous faisons avons une couverture de relevé de 100%, mais je ne dirais pas que c'était strictement nécessaire. Pendant longtemps, nous étions très proches de 100% et nous avons juste décidé de faire cela pour le dernier pour cent. Nous ne pouvions pas vraiment justifier le dernier pourcentage pour des raisons d'ingénierie, cependant; ces derniers pour cent ont été ajoutés purement comme "fierté du travail". Une couverture de 100% me dérange vraiment, mais ce n'était vraiment pas nécessaire. Nous avions une couverture de plus de 90% des déclarations juste à partir des tests normaux, ce qui nous convenait parfaitement. Cela dit, nous voulons que le logiciel soit solide, et la couverture à 100% des relevés nous a aidés à y parvenir. Il est également plus facile d'obtenir une couverture de 100% des relevés aujourd'hui.
Il est toujours utile de mesurer la couverture, même si vous n’avez pas besoin de 100%. Si vos tests ne sont pas bien couverts, vous devriez soyez concerné. Une mauvaise suite de tests peut avoir une bonne couverture de relevés, mais si vous ne disposez pas d'une bonne couverture de relevés, vous avez par définition une mauvaise suite de tests. Combien avez-vous besoin d'un compromis? Quels sont les risques (probabilité et impact) du logiciel totalement non testés? Par définition, il est plus probable qu'il y ait des erreurs (vous ne l'avez pas testé!), Mais si vous et vos utilisateurs pouvez supporter ces risques (probabilité et impact), c'est bon. Je pense que pour de nombreux projets à faible impact, une couverture de 80% à 90% des relevés de compte est acceptable, mieux vaut être meilleur.
D'un autre côté, si des personnes pouvaient mourir d'erreurs dans votre logiciel, une couverture de 100% des relevés ne suffisait pas. Je voudrais au moins ajouter une couverture de branche, et peut-être plus, pour vérifier la qualité de vos tests. Des normes telles que DO-178C (pour les systèmes embarqués) adoptent cette approche: si une défaillance est mineure, ce n'est pas grave, mais si une défaillance peut être catastrophique, des tests beaucoup plus rigoureux sont nécessaires. Par exemple, le DO-178C nécessite couverture MC/DC pour le logiciel le plus critique (le logiciel qui peut rapidement tuer des personnes s’il commet une erreur). MC/DC est bien plus astreignant que la couverture des relevés ou même la couverture des succursales.
Il ya quelque temps, j’ai fait une petite analyse de la couverture dans l’implémentation de JUnit , du code écrit et testé par, entre autres, Kent Beck et David Saff.
A partir des conclusions:
En appliquant la couverture en ligne à l’un des projets les mieux testés au monde, voici ce que nous avons appris:
Il est plus utile d'analyser soigneusement la couverture du code affecté par votre demande d'extraction que de surveiller les tendances globales de la couverture par rapport aux seuils.
Il peut être correct d'abaisser vos normes de test pour le code obsolète, mais ne laissez pas cela affecter le reste du code. Si vous utilisez des seuils de couverture sur un serveur d'intégration continue, envisagez de les définir différemment pour le code obsolète.
Il n'y a aucune raison d'avoir des méthodes avec plus de 2 ou 3 lignes de code non testées.
Les suspects habituels (code simple, code mort, mauvais temps,…) correspondent à environ 5% du code non couvert.
En résumé, devriez-vous surveiller la couverture en ligne? Toutes les équipes de développement ne le font pas, et même dans le projet JUnit, cela ne semble pas être une pratique courante. Toutefois, si vous souhaitez être aussi performant que les développeurs JUnit, rien n’explique que votre couverture de ligne soit inférieure à 95%. Et surveiller la couverture est une première étape simple pour vérifier cela.
Il y a beaucoup de bonnes informations ici, je voulais juste ajouter quelques avantages supplémentaires que j'ai découverts lorsque je visais une couverture de code à 100% dans le passé.
Comme il est plus facile de supprimer une ligne que de rédiger un scénario de test, viser une couverture à 100% vous oblige à justifier chaque ligne, chaque branche, chaque instruction if, ce qui vous conduit souvent à découvrir une façon beaucoup plus simple de faire les choses qui nécessitent moins de tests
Vous pouvez obtenir une couverture de test élevée en écrivant de nombreux petits tests testant de minuscules implémentations au fur et à mesure. Cela peut être utile pour les éléments de logique délicats, mais le faire pour chaque élément de code, même banal, peut être fastidieux, vous ralentir et devenir un véritable fardeau de maintenance rendant également votre code plus difficile à refactoriser. D’autre part, il est très difficile d’obtenir une bonne couverture de test avec des tests comportementaux de haut niveau de très haut niveau, car ce que vous testez implique généralement de nombreux composants qui interagissent de manière complexe et les permutations de cas possibles deviennent très grandes très rapidement. Par conséquent, si vous êtes pratique et que vous souhaitez également atteindre une couverture de test à 100%, vous apprendrez rapidement à trouver un niveau de granularité pour vos tests qui vous permettra d’atteindre un niveau de couverture élevé avec quelques bons tests. Vous pouvez y parvenir en testant les composants à un niveau suffisamment simple pour pouvoir couvrir tous les cas Edge, mais suffisamment compliqué pour pouvoir tester un comportement significatif. De tels tests finissent par être simples, significatifs et utiles pour identifier et corriger les bogues. Je pense que c'est une bonne compétence et améliore la qualité et la maintenabilité du code.