Qu’est-ce que le test de mutation ? (Exemple)

⚡ Résumé intelligent

Les tests de mutation consistent à introduire délibérément de petits défauts dans le code source, puis à exécuter la suite de tests existante sur chaque version défectueuse, afin de déterminer si ces tests sont suffisamment robustes pour détecter le changement.

  • (I.e. Définition: Un mutant est un programme porteur d'une modification syntaxique délibérée, et sa suppression prouve qu'un test a détecté cette modification.
  • ☑️ Processus: Générez des mutants, exécutez la suite de tests sur l'original et le mutant, comparez les résultats, puis renforcez les tests qui ont manqué des erreurs.
  • Operateurs : OperaLe remplacement de gènes, la modification de l'expression et la modification de l'instruction produisent les trois principales familles de mutants.
  • 🧪 Points : Le score de mutation correspond au pourcentage de mutants tués et mesure la force de l'assertion plutôt que la simple exécution de la ligne de code.
  • Outillage: Stryker couvre JavaScénario, TypeScript, C# et Scala, tandis que PIT modifie le bytecode JVM à l'intérieur de Maven et Gradle construit.
  • ⚠️ Coût : Chaque mutant relance l'ensemble du processus, ce qui rend les tests de mutation lents, coûteux et impraticables sans automatisation.

Test de mutation

Qu’est-ce que le test de mutation ?

Test de mutation Le test par mutation est un type de test logiciel où certaines instructions du code source sont modifiées afin de vérifier si les cas de test parviennent à détecter les erreurs. L'objectif du test par mutation est de garantir la robustesse des cas de test, en s'assurant qu'ils échouent face au code source modifié.

La modification apportée à un programme mutant doit être extrêmement minime afin de ne pas affecter l'objectif global du programme. Le test par mutation est également appelé stratégie de test basée sur les défauts, car il consiste à créer délibérément un défaut dans le programme. Il s'agit d'une forme de Blanc Box Tests qui s'applique principalement pendant Tests unitaires.

Les tests de mutation ont été proposés en 1971 dans un mémoire étudiant de Richard Lipton et formalisés en 1978 dans l'article « Hints on Test Data Selection » de DeMillo, Lipton et Sayward. Leur essor a été freiné par le coût de calcul de l'époque, mais ils ont depuis regagné du terrain pour des langages tels que… Java, C#, Python, JavaScript et XML.

Comment exécuter des tests de mutation ?

Voici les étapes à suivre pour réaliser un test de mutation, également appelé analyse de mutation :

Étape 1 : Des erreurs sont introduites dans le code source du programme en créant de nombreuses versions appelées mutants. Chaque mutant doit contenir une seule erreur, et l'objectif est de provoquer l'échec de la version mutante, ce qui démontre l'efficacité des tests.

Étape 2 : Les cas de test sont appliqués au programme original ainsi qu'au programme mutant. Cas de test devrait être adéquat, et il est modifié pour détecter les défauts dans un programme.

Étape 3 : Comparez les résultats du programme original et du programme mutant.

Étape 4 : Si le programme original et le programme modifié produisent des résultats différents, le programme modifié est éliminé par le test. Par conséquent, le test est suffisamment efficace pour détecter la différence entre le programme original et le programme modifié.

Étape 5 : Si le programme original et le programme mutant produisent le même résultat, le mutant est conservé. Dans ce cas, il est nécessaire de créer des cas de test plus efficaces qui éliminent tous les mutants.

Le diagramme ci-dessous tracIl s'agit des mêmes cinq étapes, du programme original à la génération de mutants jusqu'au verdict de survie ou de mort.

Flux de travail des tests de mutation montrant le programme original, les mutants générés, l'exécution des tests et le verdict (mutant tué ou vivant)

Comment créer des programmes mutants ?

Une mutation est simplement une modification syntaxique apportée à une instruction de programme. Chaque programme mutant doit différer du programme original par une seule mutation.

Programme original Programme mutant
Si (x>y)
Imprimer « Bonjour »
Else
Imprimer « Bonjour »
Si (x
Imprimer « Bonjour »
Else
Imprimer « Bonjour »

Dans la paire ci-dessus, seul l'opérateur de comparaison a changé ; pourtant, un test où x est supérieur à y affiche désormais « Hi » au lieu de « Hello ». L'illustration montre cette unique modification syntaxique.

Une modification syntaxique appliquée à une instruction de programme pour produire un seul mutant

Que changer dans un programme Mutant ?

Plusieurs techniques permettent de générer des programmes mutants. Les trois familles ci-dessous couvrent la plupart des opérateurs de mutation fournis avec les outils.

Operaet opérateurs de remplacement opérateurs de modification d'expression opérateurs de modification de déclaration
Remplacez l'opérande par un autre opérande (x par y, ou y par x) ou par une valeur constante. Remplacez un opérateur, ou insérez un nouvel opérateur, dans une instruction de programme. Les instructions programmatiques sont modifiées pour créer des programmes mutants.
Exemple :
Si (x> y) remplace les valeurs x et y
Si(5>y) remplacer x par la constante 5
Exemple :
Si(x==y)
On peut remplacer == par >= et obtenir le programme mutant comme suit :
If(x>=y) et insertion de ++ dans l'instruction
Si(x==++y)
Exemple :
Supprimez la partie else dans une instruction if-else
Supprimez l'intégralité de l'instruction if-else pour vérifier le comportement du programme.

Quelques exemples d'opérateurs de mutation :

  • Remplacement de l'étiquette GOTO
  • Remplacement de la déclaration de retour
  • Suppression de la déclaration
  • Insertion d'opérateurs unaires (tels que – et ++)
  • Remplacement du connecteur logique
  • Remplacement de nom de tableau comparable
  • Supprimer la partie else d'une instruction if-else
  • Ajout ou remplacement d'opérateurs
  • Remplacement de l'instruction en modifiant les données
  • Modification des données pour les variables
  • Modification des types de données dans le programme

OperaLes tores qui touchent une condition limite survivent le plus souvent, de sorte que les résultats de mutation pointent fréquemment vers des lacunes dans analyse des valeurs limites.

Types de tests de mutation

In Génie logicielLes tests de mutation sont fondamentalement catégorisés en trois types : mutation de déclaration, mutation de valeur et mutation de décision.

  • Mutation de déclaration – une instruction est coupée, collée ou supprimée, ce qui peut entraîner la suppression de certaines lignes de code.
  • Mutation de valeur – les valeurs des paramètres et constantes principaux sont modifiées, par exemple en changeant une limite de boucle ou un seuil.
  • Mutation de décision – les instructions de contrôle sont modifiées, par exemple flipping un opérateur relationnel ou la négation d'une condition.

Les outils regroupent leurs opérateurs sous ces trois rubriques, de sorte que la famille ayant produit un mutant survivant indique au testeur quel type d'assertion est manquant. Un mutant de décision survivant marque généralement une branche non testée, qui chevauche avec test en boucle.

Automatisation des tests de mutation

Les tests de mutation sont extrêmement longs et complexes à réaliser manuellement ; il est donc conseillé d’utiliser des outils d’automatisation, ce qui permet également de réduire les coûts. Un outil de test de mutation compile les mutants, planifie les exécutions, enregistre le mutant éliminé lors de chaque échec et génère un rapport de score.

Liste des outils disponibles :

  • Stryker — un framework de test de mutation open source avec des éditions pour JavaScénario et TypeScript (StrykerJS), C# et .NET (Stryker.NET), et Scala (Stryker4s).
  • PIT, également écrit PITest — un système de test de mutation pour Java et la JVM qui modifie le bytecode compilé et s'intègre à Maven et Gradle constructions à côté JUnit.

Les deux s'exécutent en tant qu'étape de compilation, elles appartiennent donc au même groupe. intégration continue pipeline comme le reste du tests d'automatisation suite.

Score de mutation

Le score de mutation est défini comme le pourcentage de mutants éliminés par rapport au nombre total de mutants.

Score de mutation = (Mutants tués / Nombre total de mutants) * 100

La formule est présentée ci-dessous sous la forme que la plupart des outils affichent.

La formule du score de mutation consiste à diviser le nombre de mutants tués par le nombre total de mutants et à multiplier par cent.

Les cas de test sont considérés comme adéquats en termes de mutation lorsque le score atteint 100 %. En pratique, le dénominateur doit exclure mutants équivalents — des mutants dont la syntaxe modifiée se comporte exactement comme l'original, de sorte qu'aucun test ne peut les éliminer. Les outils indiquent donc le nombre de mutants éliminés divisé par le nombre total de mutants non équivalents éliminés et survivants, et permettent au testeur de signaler les équivalents.

Les résultats expérimentaux ont démontré que les tests de mutation constituent une méthode efficace pour évaluer la pertinence des cas de test. Leur principal inconvénient réside dans le coût de génération des mutants et d'exécution de chaque cas de test sur chacun d'eux.

Tests de mutation vs Code Couverture

Haute Couverture de test Cela ne prouve pas la robustesse des tests. La couverture de lignes et de branches enregistre les instructions exécutées, et non si des vérifications ont été effectuées par la suite. Ainsi, un test qui appelle une méthode sans effectuer aucune assertion est tout de même considéré comme couvert. Les tests de mutation comblent cette lacune, car un mutant ne s'arrête que lorsqu'une assertion échoue.

Aspect Code couverture Score de mutation
Ce qu'il mesure Quelles lignes ou branches les tests ont-ils exécutées ? Quels défauts injectés les tests ont-ils détectés ?
Sensible aux affirmations Non, un test sans assertions ajoute tout de même de la couverture Oui — un mutant survit lorsqu'aucune assertion n'est fausse.
Coût d'une course Un essai instrumenté Un essai par mutant survivant, jusqu'à présent beaucoup plus lent
Utilisation typique Un contrôle rapide sur chaque commit Un contrôle périodique plus approfondi des modules critiques
Mode de défaillance Couverture à 100 % sans véritable vérification Des mutants équivalents qui ne peuvent jamais être tués

Ces deux indicateurs sont complémentaires. La couverture désigne le code qui n'a jamais été atteint ; le score de mutation désigne le code atteint qui n'a jamais été vérifié. Tous deux alimentent la même source. processus de gestion des défauts, parallèlement à des mesures telles que densité de défauts.

Avantages des tests de mutation

Voici les avantages des tests de mutation :

  • Il s'agit d'une approche efficace pour obtenir une couverture élevée du programme source.
  • Il teste la suite de tests elle-même, ce qu'aucun autre technique de test logiciel le fait directement.
  • Les tests de mutation offrent un bon niveau de détection d'erreurs aux développeurs de logiciels.
  • Cette méthode permet de déceler les ambiguïtés du code source et de mettre au jour des défauts que les exécutions ordinaires ne détectent jamais.
  • Les mutants survivants sont exploitables : chacun d’eux nomme une ligne spécifique et un changement spécifique que la suite n’a pas détecté.
  • Ces tests permettent aux clients de bénéficier d'un système plus fiable et plus stable.

Inconvénients des tests de mutation

En revanche, voici les inconvénients des tests de mutation :

  • Les tests de mutation sont extrêmement coûteux et chronophages, car un grand nombre de programmes mutants doivent être générés et compilés.
  • Étant donné que cette opération est chronophage, il est juste de dire que ce test ne peut être réalisé sans outil d'automatisation.
  • Chaque mutant est testé par le même nombre de cas de test que le programme original ; il faut donc exécuter une population importante de mutants sur l'ensemble de la suite de tests.
  • Les mutants équivalents ne peuvent être tués par aucun test, et leur distinction des véritables survivants nécessite généralement un examen manuel.
  • Comme la méthode modifie le code source, elle n'est pas applicable à Noir Box Tests.

Quand utiliser les tests de mutation

Le profil de coûts ci-dessus indique que les tests de mutation sont rarement exécutés sur l'ensemble du code source à chaque commit. Ils sont rentables lorsqu'une erreur non détectée coûte cher et que le code testé est suffisamment petit pour muter rapidement.

  • Logique critique pour la sécurité ou financière — calcul des paiements, règles fiscales et contrôles d'autorisation, où une mauvaise réponse silencieuse est pire qu'un accident.
  • Suites avec une couverture anormalement élevée — lorsque la couverture est proche de 100 %, mais que des défauts persistent.
  • Code existant en cours de refactorisation — Les résultats des mutations révèlent si les tests existants permettraient de détecter une régression.
  • Bibliothèques et composants partagés — un défaut dans un produit réutilisé composant se multiplie pour chaque appelant.
  • Équipes s'entraînant développement piloté par les tests — le système de vérification des scores permet de s'assurer que les tests écrits en premier sont réellement efficaces.

Il est généralement inutile de l'exécuter sur des prototypes jetables, sur du code d'interface léger ou généré sans logique de branchement, ou sur des suites dominées par des processus lents. tests d'intégration qui prennent déjà des heures pour un seul passage.

La plupart des équipes limitent donc l'analyse aux fichiers modifiés, définissent un seuil pour les modules importants et laissent le reste de l'équipe s'en charger. les tests de régression La suite transporte le reste de la cycle de vie des tests logiciels.

FAQ

Un mutant équivalent est une modification qui altère la syntaxe mais pas le comportement, comme le remplacement d'une limite de boucle jamais atteinte. Aucun test ne peut l'éliminer ; il doit donc être signalé et exclu avant que le score ne soit pris en compte.

Il n'existe pas de seuil universel. Les équipes fixent généralement un seuil élevé pour les modules critiques, comme la logique de paiement ou de sécurité, et un seuil plus bas ailleurs. Se focaliser sur un pourcentage est moins utile que d'examiner chaque code mutant survivant dans les zones à haut risque.

Ce sont les deux hypothèses sur lesquelles repose cette technique. La première postule que les programmeurs écrivent du code quasi correct, de sorte que les erreurs réelles sont mineures. La seconde affirme que les tests qui détectent les petites erreurs détectent également les erreurs complexes qui en découlent.

Limiter la mutation aux fichiers modifiés dans la branche actuelle, réutiliser les données de couverture afin que seuls les tests touchant un mutant soient exécutés, exécuter les mutants en parallèle et faire échouer la compilation en cas de baisse du score plutôt qu'en fonction d'une valeur absolue.

Les modèles d'apprentissage automatique prédisent quels mutants sont susceptibles de survivre afin que l'exécution puisse être réduite, classent les mutants probablement équivalents pour examen et génèrent des mutants qui ressemblent à des défauts observés dans l'historique du projet plutôt qu'à des échanges uniformes d'opérateurs.

Oui, pour la partie mécanique. Étant donné un mutant fonctionnel et la méthode testée, Copilot génère l'assertion manquante ou le test de cas limite. Un relecteur doit encore confirmer que la valeur attendue est correcte et ne se contente pas de copier le comportement actuel.

Il vérifie le résultat. Le développement piloté par les tests (TDD) produit des tests avant le code, mais rien ne garantit que ces tests soient suffisamment exhaustifs. Une mutation périodique exécutée sur les mêmes modules permet de vérifier si le cycle rouge-vert a produit des tests qui échouent réellement en cas de mauvaise réponse.

Non. L'injection de fautes corrompt l'environnement d'exécution et test de fuzz L'application est évaluée à l'aide d'entrées malformées. Les tests de mutation modifient le code source et évaluent la suite de tests, de sorte que l'objet évalué est différent.

Résumez cet article avec :