La couverture des tests est-elle une mesure adéquate de la qualité du code?

20

Si j'ai du code avec une couverture de test de 80% (tous les tests réussissent), est-il juste de dire qu'il est de meilleure qualité que le code sans couverture de test?

Ou est-il juste de dire que c'est plus maintenable?

David_001
la source
2
Une couverture à 100% ne signifie pas qu'elle a été bien testée. Mais 0% signifie qu'il n'a pas été testé du tout.
mouviciel
1
Techniquement non. En pratique, oui. L'expérience a enseigné à de nombreux ingénieurs logiciels et testeurs que lorsque la couverture du code atteint environ 80%, les types de défauts pour lesquels les tests unitaires sont adéquats commencent à se stabiliser. C'est le principe de pareto. Fondamentalement, une fois que vous avez atteint 80% du code, quelle que soit la qualité de vos tests, vous avez probablement testé les 20% du code qui causent la plupart des problèmes potentiels de manière assez approfondie. Ce n'est pas une sagesse absolue, mais plutôt une sagesse conventionnelle. Vous devez être plus approfondi si la vie dépend de vos tests.
Calphool
@JoeRounceville Je ne suis pas sûr ... Je peux atteindre une couverture de test élevée tout en ne testant rien de vraiment utile. La couverture vous indique simplement dans quelle mesure le code est touché par la suite de tests, et non si les tests sont significatifs.
Andres F.
1
@AndresF. C'est pourquoi j'ai dit "techniquement non, pratiquement oui". Les gens ne sont pas idiots (généralement). Ils ne testent (généralement) que des cas simples. Ainsi, sur la base de l' expérience , de nombreux magasins s'arrêtent quelque part autour de 80% de couverture, faisant l'hypothèse (assez sûre) que leurs gens ne sont pas des crétins.
Calphool

Réponses:

24

Au sens strict, il n'est pas juste de faire des réclamations tant que la qualité de la suite de tests n'est pas établie. Réussir 100% des tests n'est pas significatif si la plupart des tests sont triviaux ou répétitifs les uns avec les autres.

La question est: dans l'histoire du projet, l'un de ces tests a-t-il révélé des bogues? Le but d'un test est de trouver des bugs. Et s'ils ne l'ont pas fait, ils ont échoué en tant que tests. Au lieu d'améliorer la qualité du code, ils pourraient seulement vous donner un faux sentiment de sécurité.

Pour améliorer vos conceptions de test, vous pouvez utiliser (1) des techniques de boîte blanche, (2) des techniques de boîte noire et (3) des tests de mutation.

(1) Voici quelques bonnes techniques de boîte blanche à appliquer à vos conceptions de test. Un test de boîte blanche est construit avec un code source spécifique à l'esprit. Un aspect important du test de la boîte blanche est la couverture du code:

  • Est-ce que chaque fonction est appelée? [Couverture fonctionnelle]
  • Chaque instruction est-elle exécutée? [Couverture des relevés - La couverture fonctionnelle et la couverture des relevés sont très basiques, mais mieux que rien]
  • Pour chaque décision (comme ifou while), avez-vous un test qui le force à être vrai et un autre qui le force à être faux? [Couverture de la décision]
  • Pour chaque condition qui est une conjonction (utilisations &&) ou une disjonction (utilisations ||), chaque sous-expression a-t-elle un test où elle est vraie / fausse? [Couverture de condition]
  • Couverture de boucle: avez-vous un test qui force 0 itérations, 1 itération, 2 itérations?
  • Chacun breakd'une boucle est-il couvert?

(2) Les techniques Blackbox sont utilisées lorsque les exigences sont disponibles, mais le code lui-même ne l'est pas. Ceux-ci peuvent conduire à des tests de haute qualité:

  • Vos tests Blackbox couvrent-ils plusieurs objectifs de test? Vous voudrez que vos tests soient "lourds": non seulement ils testent la fonctionnalité X, mais ils testent également Y et Z. L'interaction de différentes fonctionnalités est un excellent moyen de trouver des bogues.
  • Le seul cas où vous ne voulez pas de tests "gras" est lorsque vous testez une condition d'erreur. Par exemple, tester une entrée utilisateur non valide. Si vous avez essayé d'atteindre plusieurs objectifs de test d'entrée non valides (par exemple, un code postal non valide et une adresse municipale non valide), il est probable qu'un cas masque l'autre.
  • Considérez les types d'entrées et formez une "classe d'équivalence" pour les types d'entrées. Par exemple, si votre code teste pour voir si un triangle est équilatéral, le test qui utilise un triangle avec des côtés (1, 1, 1) trouvera probablement les mêmes types d'erreurs que les données de test (2, 2, 2) et (3, 3, 3) trouvera. Il vaut mieux passer votre temps à penser à d'autres classes de contribution. Par exemple, si votre programme gère les taxes, vous voudrez un test pour chaque tranche de taxe. [Cela s'appelle le partitionnement d'équivalence.]
  • Des cas particuliers sont souvent associés à des défauts. Vos données de test doivent également avoir des valeurs limites, telles que celles sur, au-dessus ou au-dessous des bords d'une tâche d'équivalence. Par exemple, lors du test d'un algorithme de tri, vous voudrez tester avec un tableau vide, un tableau à un seul élément, un tableau à deux éléments, puis un très grand tableau. Vous devez considérer les cas limites non seulement pour l'entrée, mais également pour la sortie. [Il s'agit de l'analyse des valeurs limites d'appel.]
  • Une autre technique consiste à «deviner les erreurs». Avez-vous le sentiment, si vous essayez une combinaison spéciale, que vous pouvez interrompre votre programme? Alors essayez-le! Rappelez-vous: votre objectif est de trouver des bogues, pas de confirmer que le programme est valide . Certaines personnes ont le don de deviner les erreurs.

(3) Enfin, supposons que vous ayez déjà beaucoup de bons tests pour la couverture de la boîte blanche et des techniques de boîte noire appliquées. Que pouvez vous faire d'autre? Il est temps de tester vos tests . Une technique que vous pouvez utiliser est le test de mutation.

Sous test de mutation, vous apportez une modification à (une copie de) votre programme, dans l'espoir de créer un bug. Une mutation peut être:

Changer une référence d'une variable à une autre variable; Insérez la fonction abs (); Remplacez moins que par supérieur à; Supprimer une déclaration; Remplacez une variable par une constante; Supprimer une méthode prioritaire; Supprimer une référence à une super méthode; Modifier l'ordre des arguments

Créez plusieurs dizaines de mutants, à différents endroits de votre programme [le programme devra encore être compilé pour pouvoir tester]. Si vos tests ne trouvent pas ces bogues, vous devez maintenant écrire un test qui peut trouver le bogue dans la version mutée de votre programme. Une fois qu'un test a détecté le bogue, vous avez tué le mutant et vous pouvez en essayer un autre.


Addendum : j'ai oublié de mentionner cet effet: les bugs ont tendance à se regrouper . Cela signifie que plus vous trouvez de bogues dans un module, plus vous avez de chances de trouver plus de bogues. Donc, si vous avez un test qui échoue (c'est-à-dire que le test est réussi, car le but est de trouver des bogues), non seulement vous devez corriger le bogue, mais vous devez également écrire plus de tests pour le module, en utilisant le techniques ci-dessus.

Tant que vous trouvez des bogues à un rythme constant, les efforts de test doivent se poursuivre. Ce n'est que lorsqu'il y a une baisse du taux de nouveaux bogues trouvés que vous pouvez avoir confiance que vous avez fait de bons efforts de test pour cette phase de développement.

Macneil
la source
7

Selon une définition, il est plus facile à maintenir, car tout changement de rupture est plus susceptible d'être détecté par les tests.

Cependant, le fait que le code passe les tests unitaires ne signifie pas qu'il est intrinsèquement de meilleure qualité. Le code peut encore être mal formaté avec des commentaires non pertinents et des structures de données inappropriées, mais il peut toujours passer les tests.

Je sais quel code je préfère conserver et étendre.

ChrisF
la source
7

Un code sans aucun test peut être de très haute qualité, lisible, beau et efficace (ou indésirable total), donc non, il n'est pas juste de dire qu'un code avec une couverture de test de 80% est de meilleure qualité qu'un code sans couverture de test.

Il pourrait être juste de dire que le code couvert à 80% de bons tests est probablement de qualité acceptable et probablement relativement maintenable. Mais ça garantit peu, vraiment.

Joonas Pulakka
la source
3

Je dirais que c'est plus refactorisable. Le refactoring devient extrêmement facile si le code est couvert de nombreux tests.

Il serait juste de l'appeler plus maintenable.

Josip Medved
la source
2

Je serais d'accord sur la partie maintenable. Michael Feathers a récemment publié une vidéo d'un excellent discours sur son intitulé " La profonde synergie entre la testabilité et une bonne conception " dans lequel il discute de ce sujet. Dans l'exposé, il dit que la relation est à sens unique, c'est-à-dire qu'un code bien conçu est testable, mais que le code testable n'est pas nécessairement bien conçu.

Il convient de noter que le streaming vidéo n'est pas génial dans la vidéo, il peut donc être utile de le télécharger si vous souhaitez regarder en entier.

Paddyslacker
la source
-2

Je me pose cette question depuis un certain temps en ce qui concerne la "couverture conditionnelle". Alors que diriez-vous de cette page de atollic.com "Pourquoi analyser la couverture de code?"

Plus techniquement, l'analyse de la couverture du code trouve les zones de votre programme qui ne sont pas couvertes par vos cas de test, vous permettant de créer des tests supplémentaires qui couvrent des parties non testées de votre programme. Il est donc important de comprendre que la couverture du code vous aide à comprendre la qualité de vos procédures de test, pas la qualité du code lui-même .

Cela semble tout à fait pertinent ici. Si vous avez un ensemble de cas de test qui parvient à atteindre un certain niveau de couverture (code ou autre), vous invoquez très probablement le code sous test avec un ensemble assez exhaustif de valeurs d'entrée! Cela ne vous en dira pas beaucoup sur le code sous test (à moins que le code explose ou génère des défauts détectables) mais vous donne confiance dans votre ensemble de cas de test .

Dans un intéressant changement de vue de Necker Cube , le code de test est maintenant testé par le code sous test!

David Tonhofer
la source
-3

Il existe de nombreuses façons de garantir qu'un programme fait ce que vous avez l'intention de faire et de vous assurer que les modifications n'auront pas d'effets indésirables.

Les tests en sont un. Éviter la mutation des données en est une autre. Il en va de même pour un système de types. Ou une vérification formelle.

Donc, bien que je convienne que les tests sont généralement une bonne chose, un pourcentage donné de tests pourrait ne pas signifier grand-chose. Je préfère m'appuyer sur quelque chose écrit en Haskell sans test plutôt que sur une bibliothèque PHP bien testée

Andrea
la source
est-ce seulement votre avis ou vous pouvez le sauvegarder d'une manière ou d'une autre?
moucher
2
Les tests ne sont pas un moyen de garantir qu'un programme fait ce que vous avez l'intention.
Andres F.
1
Ensuite, je me demande ce que sont les tests
Andrea
@gnat c'est bien sûr mon avis. Pourtant, il dit ce qui est dit. J'ai pris Haskell comme exemple de langage dont le compilateur est très strict et donne de nombreuses garanties sur la bonne forme de l'entrée, les types, les effets secondaires, la mutation des données. J'ai pris PHP comme exemple d'un langage dont l'interprète est très indulgent et qui n'a même pas de spécification. Même en l'absence de tests, la présence de toutes les garanties du système de types et d'effets donne généralement un degré de fiabilité décent. Pour compenser cela avec des tests, il faudrait avoir une suite très complète
Andrea
J'étais peut-être un peu pressé quand j'ai écrit - j'étais au téléphone - mais je pense toujours qu'il y a un point. Je ne veux pas me baser sur PHP, mais je pense que dire qu'en comparaison Haskell donne un degré de fiabilité beaucoup plus élevé est une déclaration objective
Andrea