Couverture de code - Code coverage

En informatique , la couverture des tests est une mesure utilisée pour décrire le degré d' exécution du code source d'un programme lors de l'exécution d'une suite de tests particulière . Un programme avec une couverture de test élevée, mesurée en pourcentage, a fait exécuter une plus grande partie de son code source pendant les tests, ce qui suggère qu'il a moins de chances de contenir des bogues logiciels non détectés par rapport à un programme avec une couverture de test faible. De nombreuses métriques différentes peuvent être utilisées pour calculer la couverture de test ; certains des plus élémentaires sont le pourcentage de sous - routines de programme et le pourcentage d' instructions de programme appelées pendant l'exécution de la suite de tests.

La couverture des tests a été l'une des premières méthodes inventées pour les tests logiciels systématiques . La première référence publiée était par Miller et Maloney dans Communications of the ACM en 1963.

Critères de couverture

Pour mesurer quel pourcentage de code a été exercé par une suite de tests , un ou plusieurs critères de couverture sont utilisés. Les critères de couverture sont généralement définis comme des règles ou des exigences auxquelles une suite de tests doit satisfaire.

Critères de couverture de base

Il existe plusieurs critères de couverture dont les principaux sont :

  • Couverture des fonctions  – chaque fonction (ou sous - routine ) du programme a-t-elle été appelée ?
  • Couverture de l'instruction  – chaque instruction du programme a-t-elle été exécutée ?
  • Couverture des contours : chaque contour du graphique de flux de contrôle a-t-il été exécuté ?
  • Couverture de branche  - chaque branche (également appelée DD-path ) de chaque structure de contrôle (comme dans les instructions if et case ) a-t-elle été exécutée ? Par exemple, étant donné une instruction if , les branches true et false ont-elles été exécutées ? Il s'agit d'un sous-ensemble de la couverture périphérique.
  • Couverture de condition (ou couverture de prédicat) - chaque sous-expression booléenne a-t-elle été évaluée à la fois comme vraie et fausse ?

Par exemple, considérons la fonction C suivante :

int foo (int x, int y)
{
    int z = 0;
    if ((x > 0) && (y > 0))
    {
        z = x;
    }
    return z;
}

Supposons que cette fonction fasse partie d'un programme plus important et que ce programme ait été exécuté avec une suite de tests.

  • Si pendant cette exécution la fonction 'foo' a été appelée au moins une fois, alors la couverture de fonction pour cette fonction est satisfaite.
  • La couverture de l'instruction pour cette fonction sera satisfaite si elle a été appelée par exemple en tant que foo(1,1), car dans ce cas, chaque ligne de la fonction est exécutée, y compris z = x;.
  • Les tests appelant foo(1,1)et foo(0,1)satisferont la couverture de branche car, dans le premier cas, les deux ifconditions sont remplies et z = x;sont exécutées, tandis que dans le second cas, la première condition (x>0)n'est pas satisfaite, ce qui empêche l'exécution de z = x;.
  • La couverture des conditions peut être satisfaite avec des tests qui appellent foo(1,0)et foo(0,1). Ceux-ci sont nécessaires car dans les premiers cas, (x>0)évalue à true, tandis que dans le second, il évalue false. En même temps, le premier cas fait (y>0) false, tandis que le second fait true.

La couverture des conditions n'implique pas nécessairement la couverture des branches. Par exemple, considérons le fragment de code suivant :

if a and b then

La couverture des conditions peut être satisfaite par deux tests :

  • a=true, b=false
  • a=false, b=true

Cependant, cet ensemble de tests ne satisfait pas la couverture des branches car aucun des cas ne remplira la ifcondition.

L'injection de fautes peut être nécessaire pour s'assurer que toutes les conditions et branches du code de gestion des exceptions ont une couverture adéquate pendant les tests.

Couverture condition/décision modifiée

Une combinaison de couverture de fonction et de couverture de branche est parfois également appelée couverture de décision . Ce critère exige que chaque point d'entrée et de sortie du programme ait été invoqué au moins une fois, et que chaque décision du programme ait pris au moins une fois tous les résultats possibles. Dans ce contexte, la décision est une expression booléenne composée de conditions et de zéro ou plusieurs opérateurs booléens. Cette définition n'est pas la même que la couverture de branche, cependant, certains utilisent le terme couverture de décision comme synonyme de couverture de branche .

La couverture des conditions/décisions exige que la couverture des décisions et des conditions soit satisfaite. Cependant, pour les applications critiques pour la sécurité (par exemple, pour les logiciels avioniques), il est souvent nécessaire que la couverture condition/décision modifiée (MC/DC) soit satisfaite. Ce critère étend les critères de condition/décision avec des exigences selon lesquelles chaque condition doit affecter le résultat de la décision de manière indépendante. Par exemple, considérons le code suivant :

if (a or b) and c then

Les critères de condition/décision seront satisfaits par l'ensemble de tests suivant :

  • a=vrai, b=vrai, c=vrai
  • a=faux, b=faux, c=faux

Cependant, l'ensemble de tests ci-dessus ne satisfera pas à la couverture condition/décision modifiée, car dans le premier test, la valeur de « b » et dans le second test, la valeur de « c » n'influencerait pas la sortie. Ainsi, l'ensemble de test suivant est nécessaire pour satisfaire MC/DC :

  • a=faux, b=vrai, c= faux
  • a=faux, b= vrai , c= vrai
  • a= faux , b= faux , c= vrai
  • a= vrai , b=faux, c= vrai

Couverture de conditions multiples

Ce critère exige que toutes les combinaisons de conditions à l'intérieur de chaque décision soient testées. Par exemple, le fragment de code de la section précédente nécessitera huit tests :

  • a=faux, b=faux, c=faux
  • a=faux, b=faux, c=vrai
  • a=faux, b=vrai, c=faux
  • a=faux, b=vrai, c=vrai
  • a=vrai, b=faux, c=faux
  • a=vrai, b=faux, c=vrai
  • a=vrai, b=vrai, c=faux
  • a=vrai, b=vrai, c=vrai

Couverture de la valeur du paramètre

La couverture des valeurs des paramètres (PVC) exige que dans une méthode prenant des paramètres, toutes les valeurs communes de ces paramètres soient prises en compte. L'idée est que toutes les valeurs possibles communes pour un paramètre sont testées. Par exemple, les valeurs courantes d'une chaîne sont : 1) null, 2) vide, 3) espace (espace, tabulations, saut de ligne), 4) chaîne valide, 5) chaîne non valide, 6) chaîne à un octet, 7) double- chaîne d'octets. Il peut également être approprié d'utiliser des chaînes très longues. Ne pas tester chaque valeur de paramètre possible peut laisser un bogue. Le test d'une seule d'entre elles pourrait entraîner une couverture de code à 100 % car chaque ligne est couverte, mais comme une seule des sept options est testée, il n'y a que 14,2 % de PVC.

Autres critères de couverture

Il existe d'autres critères de couverture, qui sont moins souvent utilisés :

  • Couverture de séquence de code linéaire et de saut (LCSAJ) alias couverture JJ-Path  - tous les LCSAJ/JJ-path ont-ils été exécutés ?
  • Couverture de chemin  – Est-ce que chaque route possible à travers une partie donnée du code a été exécutée ?
  • Couverture d'entrée/sortie  – Tous les appels et retours possibles de la fonction ont-ils été exécutés ?
  • Couverture de boucle  - Chaque boucle possible a-t-elle été exécutée zéro fois, une fois et plus d'une fois ?
  • Couverture d'état  – Chaque état d'une machine à états finis a-t-il été atteint et exploré ?
  • Couverture des flux de données  – Chaque définition de variable et son utilisation ont-ils été atteints et explorés ?

Les applications critiques pour la sécurité ou fiables doivent souvent démontrer 100 % d'une certaine forme de couverture de test. Par exemple, la norme ECSS -E-ST-40C exige une couverture à 100 % des déclarations et des décisions pour deux des quatre niveaux de criticité différents ; pour les autres, les valeurs cibles de couverture sont à négocier entre le fournisseur et le client. Cependant, fixer des valeurs cibles spécifiques - et, en particulier, 100 % - a été critiqué par les praticiens pour diverses raisons (cf.) Martin Fowler écrit : « Je me méfierais de tout ce qui ressemble à 100 % - ça sentirait quelqu'un qui écrit des tests à rendre les numéros de couverture heureux, mais sans penser à ce qu'ils font".

Certains des critères de couverture ci-dessus sont liés. Par exemple, la couverture de chemin implique une couverture de décision, de déclaration et d'entrée/sortie. La couverture des décisions implique la couverture des déclarations, car chaque déclaration fait partie d'une branche.

Une couverture complète du trajet, du type décrit ci-dessus, est généralement peu pratique ou impossible. Tout module contenant une succession de décisions peut contenir jusqu'à chemins ; les constructions de boucle peuvent donner lieu à un nombre infini de chemins. De nombreux chemins peuvent également être infaisables, en ce sens qu'il n'y a aucune entrée dans le programme testé qui peut entraîner l'exécution de ce chemin particulier. Cependant, un algorithme à usage général pour identifier les chemins infaisables s'est avéré impossible (un tel algorithme pourrait être utilisé pour résoudre le problème d'arrêt ). Le test de chemin de base est par exemple une méthode permettant d'obtenir une couverture complète des branches sans atteindre une couverture complète du chemin.

Les méthodes de test de couverture de chemin pratique tentent plutôt d'identifier les classes de chemins de code qui ne diffèrent que par le nombre d'exécutions de boucle, et pour obtenir une couverture de "chemin de base", le testeur doit couvrir toutes les classes de chemin.

En pratique

Le logiciel cible est construit avec des options ou des bibliothèques spéciales et s'exécute dans un environnement contrôlé, pour mapper chaque fonction exécutée aux points de fonction dans le code source. Cela permet de tester des parties du logiciel cible qui sont rarement ou jamais accessibles dans des conditions normales, et aide à s'assurer que les conditions les plus importantes (points de fonction) ont été testées. La sortie résultante est ensuite analysée pour voir quelles zones de code n'ont pas été exercées et les tests sont mis à jour pour inclure ces zones si nécessaire. Combiné avec d'autres méthodes de couverture de test, l'objectif est de développer un ensemble de tests de régression rigoureux, mais gérable.

Lors de la mise en œuvre des politiques de couverture des tests dans un environnement de développement logiciel, il faut tenir compte des éléments suivants :

  • Quelles sont les exigences de couverture pour la certification du produit final et si oui quel niveau de couverture de test est requis ? Le niveau typique de progression de rigueur est le suivant : Énoncé, Branche/Décision, Condition modifiée/Couverture de la décision (MC/DC), LCSAJ ( Séquence de code linéaire et Saut )
  • La couverture sera-t-elle mesurée par rapport à des tests qui vérifient les exigences imposées au système testé ( DO-178B ) ?
  • Le code objet généré est-il directement traçable aux instructions du code source ? Certaines certifications, (ie DO-178B niveau A) nécessitent une couverture au niveau de l'assemblage si ce n'est pas le cas : "Ensuite, une vérification supplémentaire doit être effectuée sur le code objet pour établir l'exactitude de ces séquences de code générées" ( DO-178B ) para-6.4.4.2.

Les auteurs de logiciels peuvent examiner les résultats de la couverture des tests pour concevoir des tests supplémentaires et des ensembles d'entrées ou de configuration pour augmenter la couverture des fonctions vitales. Deux formes courantes de couverture de test sont la couverture d'instruction (ou de ligne) et la couverture de branche (ou de périphérie). La couverture de ligne rend compte de l'empreinte d'exécution des tests en termes de lignes de code qui ont été exécutées pour terminer le test. La couverture Edge indique quelles branches ou quels points de décision de code ont été exécutés pour terminer le test. Ils rapportent tous deux une métrique de couverture, mesurée en pourcentage. La signification de ceci dépend de la (des) forme(s) de couverture qui a été utilisée, car la couverture de la succursale à 67 % est plus complète que la couverture des relevés à 67 %.

En règle générale, les outils de couverture de test nécessitent des calculs et une journalisation en plus du programme réel, ce qui ralentit l'application, de sorte que cette analyse n'est généralement pas effectuée en production. Comme on pouvait s'y attendre, il existe des classes de logiciels qui ne peuvent pas être soumises à ces tests de couverture, bien qu'un degré de cartographie de couverture puisse être approximé par l'analyse plutôt que par des tests directs.

Il existe également certaines sortes de défauts qui sont affectés par de tels outils. En particulier, certaines conditions de concurrence ou opérations similaires sensibles au temps réel peuvent être masquées lorsqu'elles sont exécutées dans des environnements de test ; bien qu'à l'inverse, certains de ces défauts puissent devenir plus faciles à trouver en raison de la surcharge supplémentaire du code de test.

La plupart des développeurs de logiciels professionnels utilisent la couverture C1 et C2. C1 signifie couverture de déclaration et C2 pour couverture de branche ou de condition. Avec une combinaison de C1 et C2, il est possible de couvrir la plupart des instructions dans une base de code. La couverture des instructions couvrirait également la couverture des fonctions avec une couverture d'entrée et de sortie, de boucle, de chemin, de flux d'état, de flux de contrôle et de flux de données. Avec ces méthodes, il est possible d'atteindre une couverture de code de près de 100 % dans la plupart des projets logiciels.

Utilisation dans l'industrie

La couverture des tests est une considération dans la certification de sécurité des équipements avioniques. Les lignes directrices selon lesquelles les équipements avioniques sont certifiés par la Federal Aviation Administration (FAA) sont documentées dans les documents DO-178B et DO-178C .

La couverture des tests est également une exigence de la partie 6 de la norme de sécurité automobile ISO 26262 Véhicules routiers - Sécurité fonctionnelle .

Voir également

Les références