web-dev-qa-db-fra.com

Réduction inattendue de la couverture de code

J'ajoute des tests unitaires à du code existant qui au départ n'a pas été testé du tout.

J'ai configuré mon système CI pour échouer les builds où le pourcentage de couverture du code est réduit par rapport à la build précédente - principalement pour définir une voie d'amélioration continue.

J'ai ensuite rencontré une situation inattendue et ennuyeuse (bien que mathématiquement correcte) qui peut être réduite à ce qui suit:

  • Avant refactor:
    Méthode 1: 100 lignes de code, 10 couvertes -> 10% de couverture
    Méthode 2: 20 lignes de code, 15 couvertes -> 75% de couverture

    Total: 25/120 -> ~ 20% de couverture

  • Après refactor:
    Méthode 1: 100 lignes de code, 10 couvertes -> 10% de couverture (vierge)
    Méthode 2: 5 lignes de code, 5 couvertes -> 100% de couverture

    Total: 15/105 -> ~ 14% de couverture

Donc, même si l'OMI a amélioré la situation, mon système CI n'est évidemment pas d'accord.

Certes, il s'agit d'un problème très ésotérique, et disparaîtrait probablement lorsque la majeure partie du code sera mieux couverte, mais j'apprécierais les idées et les idées (ou les outils/configurations) qui pourraient me permettre de continuer à appliquer un chemin "d'amélioration" pour couverture avec mon système CI.

L'environnement est Java, Jenkins, JaCoCo.

35
Jonathan Gross

Vous pouvez atténuer l'effet dans une certaine mesure en permettant à la couverture de code relative de diminuer lorsque le nombre total de lignes découvertes diminue également, ou lorsque le nombre total de lignes diminue, car ce sont des signes assez clairs d'une refactorisation qui définit une nouvelle ligne de base pour vos mesures de couverture.

Dans votre exemple, le nombre total de lignes non couvertes passe de 95 lignes à 90 lignes et le nombre total de lignes de 120 à 105. Cela devrait vous donner suffisamment d'assurance que la couverture relative est assez peu importante dans cette situation. Toutefois, si vous ajoutez un nouveau code, vos mesures reflètent l'attente de ne pas autoriser une couverture relative inférieure pour le nouveau code que la couverture relative que vous aviez auparavant.

Remarque: soyez conscient, aucune de ces mesures ne vous indique si les tests couvrent les parties les plus sensibles de la base de code.

18
Doc Brown

Le problème que je vois ici est que vous avez fait la couverture du code n déclencheur pour l'échec de la construction. Je crois que la couverture de code devrait être quelque chose qui est régulièrement révisée, mais comme vous l'avez constaté, vous pouvez avoir des réductions temporaires dans votre quête d'une couverture de code plus élevée.

En général, les échecs de construction doivent être prévisibles. Les événements suivants génèrent des échecs de génération:

  • Le code ne compilera pas
  • Les tests ne s'exécuteront pas
  • Les tests échouent
  • L'emballage post-construction échoue (c.-à-d. Ne peut pas fabriquer de conteneurs, etc.)
  • Les packages ne peuvent pas être publiés dans des référentiels

Tous ces éléments sont réussis/échoués, des mesures quantitatives , ils fonctionnent (valeur binaire 1) ou non (valeur binaire 0).

La qualité du code doit être surveillée car ils sont qualitatifs . REMARQUE: les pourcentages sont une mesure qualitative même s'ils sont associés à un nombre. Voici des mesures qualitatives:

  • Complexité cyclomatique (un nombre associé à un concept qualitatif, c'est-à-dire que le nombre a une signification qualitative)
  • Indice de maintenabilité (un nombre associé à un concept qualitatif, c'est-à-dire que le nombre a une signification qualitative)
  • Pourcentage de lignes/branches/méthodes couvertes (résumé qualitatif des résultats quantitatifs)
  • Résultats d'analyses statiques (aucun chiffre impliqué)

Comme pour toute tendance, lorsque vous examinez les versions précédentes, vous pouvez trouver des réductions momentanées tandis que la tendance générale s'améliore ou reste la même (100% reste 100%). Si la tendance à long terme est vers un code moins testé ou maintenable, vous devez résoudre un problème d'équipe. Si la tendance à long terme est vers un code de meilleure qualité basé sur vos mesures, alors l'équipe fait son travail.

47
Berin Loritsch

Avez-vous envisagé de ne pas utiliser les mesures de couverture de code?

Je ne vais pas affirmer que la couverture du code n'est pas quelque chose que vous devriez regarder. C'est absolument le cas. Il est bon de garder une trace de ce qui a été couvert avant une build et après une build. Il est également bon de s'assurer que vous fournissez une couverture sur les lignes de code nouvelles et modifiées dans un changement (et, selon votre environnement, peuvent être des outils qui peuvent faciliter cela).

Mais comme vous le voyez en ce moment, vous pouvez refactoriser et finir par supprimer les lignes couvertes de sorte que le pourcentage de lignes couvertes diminue. Je suis d'accord avec votre raisonnement - la couverture logique n'a pas changé. Vous avez également probablement simplifié d'autres aspects de votre code (je serais curieux de voir comment diverses mesures de complexité ont changé avant et après votre refactoring, par exemple). Mais les calculs sont corrects - votre pourcentage de lignes couvertes a en fait diminué.

De plus, la couverture du code ne dit absolument rien sur l'efficacité ou la qualité des tests. Ils ne disent également rien du risque derrière la partie du code. Il est relativement facile d'écrire des tests qui couvrent des lignes triviales ou de vérifier la logique la plus simple sans donner trop confiance à la qualité du système.

37
Thomas Owens

Cela s'appelle paradoxe de Simpson, et c'est un problème statistique bien connu avec votre approche.
Vous pourriez même construire des cas où vous refactorisez et ensuite chaque méthode a une couverture plus élevée, mais la couverture globale a quand même baissé.

Vous devrez trouver d'autres façons d'attraper les "régressions" du type que vous vouliez attraper, pas avec des pourcentages globaux (bien que j'aie aimé l'approche en la lisant).

14
Aganju

Bien que toutes les réponses disent de ne pas utiliser la couverture de code comme mesure de qualité, aucune n'a vraiment répondu à la question. Que faire avec une couverture réduite?
La réponse est assez simple: utilisez des nombres absolus, pas des pourcentages. Ce qui est plus important que le pourcentage de couverture, c'est le nombre de fonctions non testées et le nombre de branches non testées. En outre, il serait intéressant d'obtenir la liste des fonctions et branches non testées.

5
BЈовић

Une option qui pourrait aider à cela est de passer de la couverture de ligne à la couverture de succursale. Je dis "pourrait" aider parce que vous pouvez rencontrer une situation similaire avec des branches découvertes si vous réduisez les branches dans une fonction.

Dans l'ensemble, cependant, la couverture des succursales est une mesure plus significative que la couverture des lignes. Et je vais risquer de deviner que lorsque vous refactorisez une méthode longue en une méthode courte, vous réduisez généralement les lignes de code par rapport aux branches. Bien sûr, si c'est vraiment du code désagréable, vous pourriez finir par tailler beaucoup de branches. Même dans ce cas, je pense qu'il vaut mieux compter les branches.

2
JimmyJames

Ce que vous faites n'est pas faux. Bien que la couverture du code ne soit pas une métrique étonnante, c'est une métrique utile. Cela ne vous dit pas que votre code est parfaitement testé. Mais ne pas avoir un certain niveau de norme que vous attendez de vos tests est également une mauvaise idée.

Une façon de résoudre ce problème consiste à simplement modifier la limite si vous pensez que le changement est justifié. L'intérêt de ce type de limite stricte est de vous avertir rapidement en cas de problème. Dans de nombreux cas, cette réduction sera involontaire et doit être corrigée en introduisant des tests plus nombreux et de meilleure qualité. Mais si vous identifiez l'erreur comme étant un faux positif et que vous pouvez prouver à vous-même et à votre équipe que la réduction est attendue, alors il est parfaitement correct de réduire la limite.

Une autre façon est de transformer cette réduction en échec "léger". Par exemple, Jenkins peut avoir un résultat "instable" d'une construction. Dans une version instable, les artefacts finaux sont toujours utilisables, mais il peut y avoir une certaine dégradation des fonctionnalités. Cela vous donne le temps de décider quoi faire, si les tests doivent être étendus ou les limites abaissées. Cela pourrait également être implémenté à l'aide de deux versions différentes: une qui a les artefacts de génération avec des contrôles et des limites moindres, qu'en cas d'échec, même les artefacts ne sont pas utilisables. Et deuxièmement, avec des limites beaucoup plus strictes qui échoueraient si l'une de vos mesures de qualité était cassée.

2
Euphoric

En plus de répondre de Berin Loritsch, je voudrais également mettre en évidence une technique appelée Mutation Testing (voir https://pitest.org/ pour un Java) .

Le test de mutation fournit des mesures sur lesquelles les lignes de codes peuvent être mutées pour se comporter différemment et ne pas entraîner l'échec du test unitaire.

Dans votre cas, si le refactoring a été bien fait, vous devriez voir les mesures de test de mutation s'améliorer (lorsqu'elles sont exprimées en pourcentage de la couverture de la ligne de test unitaire).

Ce lien décrit quelques outils de test de mutation (liés à Java): https://pitest.org/Java_mutation_testing_systems/

1

Avant refactor: 95 lignes non testées.

Après refactor: 90 lignes non testées.

Vous devez toujours considérer le nombre de lignes non testées que vous avez!

Le pourcentage de couverture du code n'est une bonne mesure de cela que si votre code croît toujours, ce qui est regrettable.

Une autre façon de le dire: chaque fois que vous parvenez à réduire le nombre de lignes dans votre code, sans casser aucun test, vous avez tous les droits d'ignorer le pourcentage de couverture du code!

1
Pedro A

Le problème est: vous avez une fonction énorme avec très peu de couverture de code, donc pour augmenter le pourcentage, vous devez ajouter des lignes de code couvertes. Si vous ajoutez 20 lignes/10 couvertes, cela améliore le pourcentage de plus de 5 lignes/5 couvertes. Même si la seconde est probablement meilleure (à moins que la vitesse ne soit importante et que votre première fonction soit "si (cas facile) {gérer les cas faciles rapidement} sinon {gérer les cas complexes lentement}" et la seconde était "gérer tous les cas lentement") .

Nous concluons donc: vous avez une chose mesurable où l'amélioration de la mesure n'améliore pas la qualité de votre code. Ce qui signifie que vous ne devez l'utiliser comme cible que lorsque le bon sens est appliqué en même temps. À partir d'un livre sur la "motivation": La chose la plus importante est de motiver les gens de telle sorte que suivre la motivation donne de meilleurs résultats.

Imaginez qu'il existe une fonction avec une ligne "int x = 100;". Pour améliorer la couverture du code, je le change en "int x = 0; x ++; x ++; (répéter 100 fois)". Au lieu d'une ligne couverte, j'ai 101 lignes couvertes. La couverture totale du code augmente et j'obtiens un bonus.

0
gnasher729

Voici une solution qui n'implique pas de modifier la définition de la mesure de couverture du code ou de demander/vous accorder une exemption à la politique. Bien que, comme indiqué, la politique soit sans doute défectueuse et il serait préférable de compter le nombre total de lignes non couvertes ou d'appliquer une limite inférieure par fichier (par exemple 80%) au lieu d'exiger la monotonie.

Améliorez la couverture du code de la méthode 1 avant ou en même temps que la refactorisation de la méthode 2.

Voici la situation actuelle indiquée dans la question.

                    Old World                   New World

           covered  total   %           covered  total   %
 Method 1       10    100  10                10    100  10
 Method 2       15     20  75                 5      5 100
Aggregate       25    120  20.8              15    105  14.3

Dans le Nouveau Monde, le nombre total de lignes couvertes doit être d'au moins 22 pour que la mesure ne parvienne pas à diminuer. (Le plafond de 105 * 25 / 120 est 22).

Donc, tout ce que vous devez faire est d'ajouter des tests qui couvrent 7 lignes supplémentaires du fichier où Method 1 et Method 2 vivre.

Cette solution présente l'inconvénient d'inclure une modification non liée dans le même commit afin de satisfaire la règle de monotonie de la couverture du code. Il pourrait être utile de le faire si ce seul changement à la méthode 2 n'est pas suffisamment important pour justifier un changement de politique. Il peut également être utile de le faire avant de modifier la stratégie.

0
Gregory Nisbet