TEST DE MUTATION AUTOMATISE AVEC TESSY

Publié par Nicolas le

Test de mutation TESSY

Vos cas de tests sont ils pertinents ?

Les tests de mutation permettent de vérifier la qualité de vos cas. Le test de mutation automatisé est la nouvelle fonctionnalité la plus importante de la nouvelle version principale V4.3 de TESSY, l’outil pour le test unitaire automatisé de module et d’intégration de logiciels embarqués.

TESTS DE MUTATION AUTOMATISE AVEC TESSY

Les tests de mutation répètent l’exécution de cas de test qui ont déjà été passés pour un objet de test donné, par exemple une unité logicielle. Toutefois, bien que les cas de test soient répétés, ils ne seront pas exécutés avec le code source original de l’objet de test, mais avec un code qui aura été modifié (“muté”).

 Le code muté est différent du code original. En effet, les changements peuvent concerner des détails mineurs, tels que le remplacement d’un ET logique par un OU logique. Cependant, les changements peuvent également être drastiques, tels que la suppression de la branche “else” d’une instruction if.

Bien entendu, l’objet de test doit rester compilable même après la modification, sinon une répétition du test n’est pas possible. Lorsque le test est répété avec le code source muté, la question est de savoir si les cas de test existants révèlent la mutation (le terme technique est “kill”).

Une mutation est tuée si au moins un cas de test échoue lorsque le test est répété. Si ce n’est pas le cas, les cas de test ne détectent pas que le code source a été modifié, ou en d’autres termes : les cas de test considèrent également comme correct un objet de test autre que l’original.

Cette situation est ennuyeuse  et doit être examinée plus avant (pour cette investigation, il est utile qu’une seule mutation ait été effectuée). La qualité des cas de tests fait défaut si la mutation n’est pas équivalente. Une mutation équivalente ne modifie pas le comportement vers l’extérieur.

Un exemple de mutation équivalente est donné plus loin (fig. 8).

De même, le degré de radicalité de la mutation est important ; une mutation subtile sera plus difficile à détecter que plusieurs changements drastiques. En général, on procède à plusieurs tests avec différentes mutations. Cela permet d’évaluer la qualité du cas test.

TESSY automatise entièrement le processus de test de mutation
Fig. 1: TESSY automatise entièrement le processus de test de mutation

La figure ci-dessus (fig. 1) montre le processus de test de mutation tel qu’il est effectué automatiquement par TESSY. Une fois que le code source original a passé tous les tests existants, le processus de test proprement dit peut être lancé.

TESSY effectue exactement une mutation et répète tous les tests existants. A  cette occasion il sera enregistré si une mutation a été tuée ou non. Ensuite, l’objet de test original est restauré et une autre mutation est effectuée. La figure 2 montre les mutations que TESSY peut effectuer depuis la version V4.3.

L’utilisateur peut sélectionner les mutations à appliquer et ainsi bien sûr influencer le nombre de mutations effectuées, ce qui à son tour affecte le temps d’exécution de l’ensemble du processus de test de mutation.

Test par défaut
Fig. 2: Les mutations par défaut proposées par TESSY V4.3

Deux hypothèses pour les tests de mutation

Les mutations effectuées par TESSY par défaut sont subtiles. Par exemple l’opérateur relationnel ‘<‘ devient ‘<=’. Ceci est basé sur l’hypothèse du “programmeur compétent” [cf biblio :Liggesmeyer], qui dit qu’un développeur de logiciel compétent ne fait que des erreurs mineures, par exemple lorsqu’une boucle est exécutée une fois de trop ou une fois de trop peu (erreur “off-by-one”), ou lorsqu’un tableau accède à une cellule de stockage qui n’appartient plus au tableau.

Afin de savoir si les cas de test trouvent des erreurs aussi mineures et sont donc de bonne qualité, les mutations doivent être subtiles. Les mutations radicales, telles que la suppression d’une ou même de plusieurs instructions, doivent également être révélées par des cas tests de faible qualité. 

Une autre hypothèse empiriquement confirmée [biblio :Offut] affirme qu’il y a un effet de couplage : un cas test qui tue exactement un mutant tue aussi plusieurs mutations. Il suffit donc d’effectuer une seule mutation à la fois.

Exemple

Nous considérons un objet de test qui a passé quatre cas de test (fig.3) et a atteint une couverture de code de 100% avec ces cas de test.

Le premier objet de test passe ces quatre tests
Fig. 3: Le premier objet de test passe ces quatre tests

Si TESSY effectue le test de mutation (avec les réglages standard pour la mutation comme dans la fig. 2), le résultat est un mutant tué et un mutant survivant (fig. 4).

Résultat du test dans TESSY
Fig. 4: Résultat du test de mutation dans TESSY

Dans la partie supérieure gauche de la figure ci-dessus (fig. 4), la mutation tuée (“mutation caused test failure”) est indiquée. Cette mutation a fait passer l’opérateur relationnel de la première instruction if de l’objet de test (surligné dans la partie supérieure droite de la figure X4) de “<” à “<=”.

En conséquence, un cas test échoue, ce qui est positif dans les tests de mutation. Par conséquent, cette mutation est cochée en vert. Dans la partie inférieure gauche de la fig. X4, vous pouvez voir la mutation survivante (“mutation survived all test cases “) ; cette mutation a fait passer l’opérateur relationnel de la deuxième instruction if de l’objet de test (surligné dans la partie inférieure droite de la fig. X4) de “>” à “> =”.

Aucun test n’a détecté ce changement en échouant. Cette situation est discutable et doit être examinée.

Score de mutation et qualité des cas de test

Le score de mutation est le rapport entre les mutations tuées et l’ensemble des mutations.

Le score dans TESSY
Fig. 5: Le score de mutation dans TESSY

La figure ci-dessus (fig. 5) montre le score de mutation des quatre cas de test déterminés par TESSY. Le cas de  test 2 a tué l’un des deux mutants, ce qui donne un score de mutation de 50 %. Le cas de test 2 est coché en vert dans la colonne M parce qu’il a tué un mutant. Les trois autres cas de test n’ont tué aucun mutant et présentent donc une croix rouge ou un score de mutation de 0 %.

Le cas de test 2 a tué un mutant. Il est donc de meilleure qualité que les autres cas de tests qui n’ont tué aucun mutant. Cela est dû à la valeur de la variable v1 dans le cas de test 2. Cela dépend de l’opérateur relationnel dans la première instruction if. Dans le deuxième cas de test, la variable v1 et la variable r1.range_start ont toutes deux la valeur 5, ce qui signifie que la décision prise dans la première instruction if est “5 < 5”, ce qui résulte en “faux”. Dans la mutation, la décision est “5 <= 5”, est évaluée à un “vrai”. Par conséquent, le deuxième cas de test donne un résultat inattendu (“non” au lieu du “oui” correct et attendu), ce qui tue le mutant.

Le cas de test 4 devrait tuer l’autre mutation dans la décision de la deuxième instruction if. Mais cela ne fonctionne pas car la valeur de v1 dans le cas de test 4 est inappropriée. La variable v1 a la valeur 9 et r1.range_start + r1.range_len donne 5 + 2 = 7. Ainsi, la décision dans la deuxième instruction if est dans le cas original ‘9 > 7’ et dans le cas mutant ‘9 >= 7’, qui sont tous deux évalués comme “vrais”. Ainsi, l’original et le mutant donnent le résultat correct “non” dans les deux cas ; l’original et le mutant passent le quatrième cas test ; cela signifie que le mutant n’est pas tué.

Le cas de test 2 est de meilleure qualité que le cas de test 4 car le cas de  test 2 utilise une valeur limite et le cas de test 4 ne le fait pas. Le cas le test 2 utilise la valeur limite 5 qui est la valeur de départ de la plage qui commence à 5 et a la longueur 2 et contient donc les valeurs 5 et 6.

Avec la valeur 9 pour la variable v1, le cas de test 4 n’utilise pas de valeur limite de l’intervalle. Cela démontre pourquoi les valeurs limites constituent de bonnes données de test et pourquoi les normes de développement de logiciels critiques pour la sécurité recommandent les valeurs limites comme données de test.

Par exemple, la norme IEC 61508 [biblio : 61508] recommande la méthode de “l’analyse des valeurs limites” dans les tableaux B.2 et B.3 de la partie 3. Dans les deux tableaux, cette méthode est recommandée pour le SIL 1 et fortement recommandée pour les SIL 2 à 4. La norme ISO 26262 [biblio :26262] mentionne également comme méthode 1c dans le tableau 8 de la partie 6 “analyse des valeurs limites” une procédure pour obtenir des données d’essai pour les tests unitaires de logiciels. La méthode est recommandée pour ASIL A et fortement recommandée pour ASIL B à D

Les tests de mutation peuvent également évaluer des séries de cas de test. Un ensemble de cas tests est dit adéquat s’il tue tous les mutants. Plus l’ensemble de cas tests adéquats est petit, mieux c’est. Il peut également être utilisé pour évaluer les méthodes de construction des cas tests.

Boucles sans fin et Plantages

Les mutations peuvent également entraîner des boucles sans fin, ce qui signifie qu’un test n’est pas terminé. Pour qu’une telle mutation ne mette pas un terme à l’ensemble du processus, les tests (y compris les tests de non mutation, bien sûr) peuvent bénéficier d’un timeout dans TESSY. En ce qui concerne les tests de mutation, l’apparition d’une boucle sans fin ou, pour être précis, le timeout, tue la mutation. Une mutation peut également provoquer un crash de l’objet testé, par exemple en raison d’une division par zéro. Un crash de l’objet de test muté tue le mutant. Après un timeout ou un crash, le processus de test de mutation est poursuivi, si d’autres mutations sont applicables.

Une boucle infinie tue le mutant.
Fig. 6: Une boucle infinie tue le mutant.

Dans l’exemple ci-dessus (fig. 6), la fonction count() est testée avec un scénario de test qui a la valeur d’entrée 10 pour le paramètre x et renvoie le résultat correct en renvoyant la valeur 1. Ce scénario de test tue les quatre mutations, comme le montre la partie gauche de la figure 6. La troisième mutation (de ‘<=’ à ‘>=’) n’est pas tuée par l’échec du cas test, comme d’habitude, mais par une boucle sans fin. TESSY est réglé de manière à ce que les tests soient automatiquement interrompus après 10 secondes d’exécution. Cela se produit avec la troisième mutation et TESSY considère cette mutation comme tuée. Après cela, la quatrième mutation est effectuée

Un plantage tue le mutant
Fig. 7: Un plantage tue le mutant

Dans l’exemple ci-dessus (fig. X7), la fonction crash_by_divide() est testée avec un scénario de test dans lequel les deux paramètres a et b ont la même valeur. Ce cas de test tue la mutation de ‘!=’ en ‘==’ dans la décision de l’instruction if.

Les mutations équivalentes posent problème

Le principal problème des tests de mutation est celui des mutations équivalentes. Ces mutations ne modifient pas le comportement externe de l’objet testé. Par conséquent, une telle mutation ne peut pas être tuée par un cas de test. En conséquence, toutes les mutations survivantes doivent être vérifiées manuellement (par un humain) pour déterminer s’il s’agit ou non d’une mutation équivalente. Cela peut prendre beaucoup de temps. Toutefois, il est utile ici que, comme pour TESSY, une seule mutation soit effectuée à la fois. En outre, les objets testés sont des unités logicielles, qui sont petites par rapport à l’ensemble du logiciel. Cela réduit l’effort de vérification des mutations équivalentes.

Par-dessus tout, nous pouvons supposer que les logiciels, qui sont soumis à des tests unitaires, ont de meilleurs scénarios de test que les autres logiciels, car ces logiciels sont souvent à sécurité critique et doivent donc atteindre un pourcentage élevé de couverture de code. Cela signifie que seule une petite partie du logiciel (le cas échéant) n’est exécutée par aucun scénario de test.

D’un autre côté, d’autres logiciels, qui ne sont pas testés de manière aussi approfondie que les logiciels critiques, peuvent contenir d’énormes parties de code qui ne sont pas exécutées par un scénario de test. Il est évident qu’une mutation dans une partie du logiciel qui n’est pas exécutée par un scénario de test ne peut pas être tuée par un scénario de test. Cela signifie un nombre plus élevé de mutants survivants et donc un effort plus important pour choisir entre des cas de test insuffisants et des mutants équivalents.

Les mutations équivalentes peuvent être considérées comme des mutations tuées. Elles n’indiquent pas de cas de test de faible qualité.

Exemple de mutation équivalente
Fig. 8: Exemple de mutation équivalente

Dans la figure ci-dessus (fig. 8), on montre  une mutation équivalente. La mutation de l’opérateur de comparaison relationnelle de ‘>’ à ‘>=’ n’a pas d’effet visible de l’extérieur et ne peut donc être éliminée par aucun cas test. Mais la valeur d’entrée 0 provoque à coup sûr un comportement de programme interne différent entre le code source original et le code source muté. Dans le code original, la branche “else” de l’instruction if est exécutée, tandis que la mutation exécute la branche “then”.

Le test de mutation dans les normes

La norme IEC 61508 décrit les tests de mutation comme ” test case execution from error seeding ” et le recommande pour les niveaux d’intégrité de sécurité (SIL) 2 à 4 (dans le tableau B.2 de la partie 3).

La norme ISO 26262 ne mentionne les “mutations de code” que dans une note relative à la méthode “Fault Injection Test” (méthode 1l) dans le tableau 7 de la partie 6, qui énumère les méthodes de vérification des unités logicielles.

Cependant, les “mutations de code” sont mises en commun avec des procédures de test de robustesse telles que la modification arbitraire (corruption) des mémoires de données ou des registres de l’unité centrale. En corrompant les valeurs des variables dans la mémoire de données, par exemple, l’objet de test ne relit pas les valeurs qui y étaient précédemment stockées et le but du test est de déterminer si l’objet de test peut y faire face.

Pour le dire plus concrètement : L’objet de test reconnaît-il (par exemple en raison d’un stockage redondant des valeurs), par exemple, une inversion de bit causée par le rayonnement cosmique et y réagit-il de manière appropriée ? Une telle question diffère fondamentalement de l’objectif du test de mutation, à savoir évaluer la qualité des cas de test et détecter les erreurs de programmation grâce à de meilleurs cas de test, peut-être.

Conclusion

Les tests de mutation peuvent révéler des cas de tests insuffisants. Leur amélioration augmente les chances de trouver des erreurs dans les logiciels testés. Par conséquent, les tests de mutation n’évaluent pas seulement la qualité des cas de test, mais peuvent également contribuer à une meilleure qualité des logiciels testés. L’exécution du test de mutation est automatisée dans TESSY, de sorte que l’exécution n’implique pas d’effort supplémentaire

Terminologie

Error seeding

C’est ainsi que la norme IEC 61508 appelle les tests de mutation (voir section C.5.6 de la partie 7)

Effet de couplage

Si un mutant présentant une seule mutation est découvert par un ensemble de cas de tests, des mutations multiples sont également découvertes.

Test de mutation fort

Le mutant n’est considéré que de l’extérieur (boîte noire) et le mutant n’est découvert qu’à travers un cas de test, qui extérieurement donne un résultat différent de l’original.

Test de mutation faible

Un cas de test assure un comportement différent à l’intérieur du mutant par rapport à l’original. Cependant, cet autre comportement n’est pas visible de l’extérieur.

Cas de test adéquat / ensemble de cas de test adéquat

Un cas test est dit adéquat s’il tue un mutant non équivalent. Un ensemble de cas test est dit adéquat s’il tue tous les mutants non équivalents.

Score de mutation

C’est le ration entre le nombre de mutants tués par le nombre de mutants. Est exprimé en pourcentage.

Injection d’erreurs
Des erreurs externes sont injectées dans l’objet de test non muté afin de tester sa robustesse. La norme ISO 26262 mentionne les “mutations de code” comme exemple pour le test d’injection d’erreurs.

Liens

[TESSY]        http://www.hitex.com/tessy

[NeoMore]  Page sur TESSY

Bibliographie

 [26262]         ISO 26262, International Standard, Road vehicles – Functional Safety, second edition, 2018

[61508]          IEC 61508, Functional safety of electrical /electronical/programmable electronic safety related systems, second edition, 2010.

[Liggesmeyer]          Liggesmeyer, Peter: Software-Qualität: Testen, Analysieren und Verifizieren von Software. 2. Auflage, Heidelberg, Berlin, 2009. Spektrum Akademischer Verlag.

[Hoffmann]   Dirk W. Hoffmann, Software-Qualität. Springer-Verlag Berlin Heidelberg, 2008.

[Offut]             A. Jefferson Offut, Clemson University: Investigations of the software testing coupling effect, in ACM Transactions on Software Engineering and Methodology, New York, Volume 1 Issue 1, Jan. 1992.

L' Auteur

Frank Büchner - TESSY - Hitex

Frank Büchner a étudié l’informatique à l’Université technique de Karlsruhe, aujourd’hui l’Institut de technologie de Karlsruhe (KIT). Depuis l’obtention de son diplôme, il a passé plus de trente ans à occuper différents postes dans le domaine des systèmes embarqués. Au fil des ans, il s’est spécialisé dans les tests et la qualité des logiciels embarqués. Il travaille actuellement chez Hitex GmbH, Karlsruhe, en Allemagne, en tant qu’ingénieur principal en qualité logicielle.

.

Catégories : Logiciel Enfoui