• Aucun résultat trouvé

2.3 Test de logiciel

2.3.2 Analyse de mutation

L’analyse de mutation est une technique pour la qualification de données de test fondée sur l’injection d’erreurs. Nous étudions l’adaptation de l’analyse de mutation au test de transformations de modèles dans le chapitre 3. Par ailleurs, l’insertion systématique d’erreurs dans un programme peut être utilisée pour la vérification expérimentale de techniques de test. La connaissance du nombre, de l’emplacement, et de la nature des erreurs dans le programme permet de contrôler l’efficacité de techniques de test ou de diagnostic [Jones'02b]. Nous réalisons une telle exploitation dans le chapitre 5 pour qualifier des oracles et des composants de transformation de modèles.

L’analyse de mutation a été proposée par DeMillo [DeMillo'78]. Cette technique peut être employée dans deux buts : évaluer la qualité d’un ensemble de cas de test et assister la génération de test. Dans les deux cas, la technique consiste à créer un ensemble de versions erronées du programme sous test, appelées mutants, puis à exécuter un ensemble de cas de test sur chacun des mutants. Un mutant est une copie du programme sous test qui diffère par l’injection d’une seule erreur. En pratique, l’ensemble des mutants est créé en appliquant sur le programme des opérateurs de mutation qui correspondent à différents types d’erreur (remplacement d’opérateurs arithmétiques ou logique, changement de signe des constantes et variables, remplacement d’appel de méthode…). Les erreurs sont injectées de manière maîtrisée et systématique dans le programme.

Le résultat de l’exécution des cas de test avec les mutants permet d’une part d’évaluer l’efficacité de l’ensemble de cas de test par la proportion de mutants détectés (appelée le score de mutation). D’autre part, l’analyse des erreurs qui n’ont pas été détectées permet de guider la génération de nouveaux cas de test. Ceci consiste à analyser les erreurs des mutants, puis à créer des données de test spécifiques pour provoquer un comportement différentiable du mutant et du programme sous test. Si un cas de test peut détecter l’erreur d’un mutant, il tue le mutant, sinon le mutant est vivant.

Chaque mutant ne comporte qu’une seule erreur simple. Cette limitation est fondée sur deux hypothèses :

33 Test de logiciel

ƒ le programmeur est compétent, ƒ l’effet de couplage.

La première hypothèse est fondée sur la compétence du programmeur. Il peut écrire un programme incorrect mais qui est néanmoins « proche » de la version correcte (les modifications pour le corriger restent mineures). Dans le cas d’erreurs grossières, les tests les plus simples mettraient facilement à jour l’incompétence du programmeur. L’hypothèse sur l’effet de couplage dit que si des tests peuvent détecter les erreurs simples, alors ils pourront détecter des erreurs plus complexes. Offutt définit les fautes simples et complexes commises par le programmeur dans [Offutt'92] :

Faute simple, faute complexe : Une faute simple est une faute qui peut être corrigée par une

seule modification d’instruction [du programme]. Une faute complexe est une faute qui ne peut pas être corrigée par une seule modification d’instruction.

Dans ce même article, Offutt étudie l’hypothèse sur l’effet de couplage. Il compare l’exécution de cas de test efficaces avec des mutants ne comportant qu’une seule erreur et des mutants comportant plusieurs erreurs. Les cas de test détectant des erreurs simples détectent aussi des erreurs combinant plusieurs erreurs simples, ce qui tend à valider l’hypothèse sur l’effet de couplage.

Plusieurs travaux portent sur la comparaison de l’efficacité de l’analyse de mutation avec des critères de couverture de flots de données [Wong'93, Offutt'96b, Mathur'94, Frankl'97]. Par exemple, Offutt et al. mettent en évidence que les cas de test construits avec l’analyse de mutation satisfont plus facilement les critères de flot de données. Ils découvrent également plus d’erreurs alors que dans les deux cas l’effort est le même pour créer des données de test. Dans [Andrews'05], les auteurs ont expérimentés l’analyse de mutation avec des programmes contenant de véritables erreurs. Ces erreurs n’avaient pas été injectées à des fins expérimentales mais avaient été insérées pendant le développement de huit logiciels existants. Les résultats qu’ils obtiennent confirment l’intérêt de l’analyse de mutation pour la qualification de données de test capable de détecter les véritables erreurs. Ils appuient également l’hypothèse qui considère que le programmeur est compétent.

Les erreurs injectées en appliquant les opérateurs de mutation correspondent à différents types d’erreurs de programmation. Ces types ont été identifiés en observant les pratiques des programmeurs et en analysant les erreurs détectées lors de tests. La plupart des travaux produisent des opérateurs de mutation dédiés aux langages d’implantation des programmes sous test : C [Barbosa'01, Agrawal'89], ADA [Offutt'96c], Java [Ma'02]. Ces opérateurs s’appuient sur la syntaxe des langages pour injecter les erreurs. La mise au point des opérateurs dépend du paradigme de programmation utilisé : procédural [Agrawal'89], orienté objet [Ma'02, Kim'01, Chevalley'01, Alexander'02], ou plus récemment la programmation par aspect [Ferrari'08]. Les opérateurs peuvent être dédiés à un niveau de test : unitaire [Baudry'00a], intégration [Hoijin'98, Ghosh'01, Delamaro'01]. Dans le chapitre suivant, nous proposons des opérateurs de mutation spécifiques aux transformations de modèles.

Face à l’augmentation du nombre d’opérateurs de mutation, certains travaux se sont intéressés à minimiser ce nombre. Ainsi, Offutt étudie le sous-ensemble suffisant pour les langages procéduraux dans [Offutt'96a], de même Borbosa effectue ce travail pour le langage C [Barbosa'01].

Des travaux récents ont mis au point des opérateurs pour l’ingénierie des modèles. Dans [Trung'05], Trung et al. dressent une taxonomie des fautes qui peuvent être commises dans les diagrammes UML. Dans [Sen'06], Sen et al. les exploitent pour la génération de modèles en entrée de transformations de modèles.

Des travaux ont montré qu’augmenter le score de mutation d’un ensemble de données de test au-delà de 95% est généralement difficile [Frankl'97, DeMillo'93]. Leurs auteurs en déduisent que ces ensembles sont suffisamment efficaces. D’autres expériences montrent que les cas de test fournis par le testeur détectent souvent entre 50 et 70% des mutants [Baudry'03]. La création des données de test pour tuer près de la moitié des mutants (de 50% à 95%) est fastidieuse, puisqu’il faut analyser pourquoi leurs erreurs ne sont pas révélées, puis produire une donnée de test pour tuer chacun d’entre eux.

Dans [DeMillo'91], les auteurs proposent une technique pour la génération automatique de cas de test en exploitant l’analyse de mutation. Ils développent l’idée de fixer comme objectif de test la position de l’erreur dans le programme, et de remonter toutes les contraintes sur les données entre cette position et l’entrée du programme. Avec cette méthode, ils générent une donnée de test qui atteint la position de l’erreur et tue le mutant. Un outil pour le langage Fortran supporte ce travail [DeMillo'93]. Dans [Chevalley'01] les auteurs détaillent une technique de génération automatique pour des mutants de programmes JAVA fondée sur le mécanisme d’introspection du langage.

De nombreux outils ont été proposés pour mettre en œuvre l’analyse de mutation : Mugamma [Kim'06], Mujava [Ma'05], CREAM system [Derezinska'08], NMutator [Baudry'05], JMutator [Baudry'06b], Jester [Moore'01].