Processus du test
Le test est une activité dont le but est de détecter les fautes présentes dans un programme. Un test peut être statique ou dynamique. Avec un test statique, le programme sous test est validé sans être exécuté. Cette validation se fait par analyse statique de code, automatiquement ou manuellement. Avec un test dynamique, le programme sous test est exécuté pour provoquer sa défaillance et révéler la présence d’une faute. Les techniques de test utilisées sont liées à la manière dont est conçu et mis en œuvre le programme sous test. Par exemple, dans le cycle classique de développement logiciel en V (Figure 2.1) il existe plusieurs types de test correspondant aux différentes phases de développement. Un test unitaire vérifie une unité du programme sous test : une classe ou une méthode dans le cas de programmation orientée objet. Un test d’intégration, lui vérifie les interactions entre plusieurs unités du programme. Les tests de validation ou tests systèmes vérifient la totalité du système en intégrant les groupes d’unités validés par les tests d’intégration. Les tests de recettes quant à eux ont pour but de valider le système par rapport aux attentes du client, définies dans la spécification. Pour tester un programme, le testeur écrit une suite de test, composée de cas de test. Un cas de test correspond à une exécution du programme à tester, le programme sous test ou System Under Test (SUT). Pour chaque cas de test, comme le montre la Figure 2.2, le testeur sélectionne une donnée de test (DT) qui est une donnée d’entrée du programme sous test. Il exécute ensuite le programme sous test sur la donnée de test et produit un résultat (Res). L’oracle du cas de test contrôle le résultat produit et fournit le verdict du cas de test. Nous reparlerons en détail de l’oracle dans la prochaine section. Si le résultat est correct par rapport à la spécification, le test passe, sinon il échoue. Lorsqu’une faute est détectée par l’oracle, il faut la corriger. Lorsque cette correction introduit une nouvelle faute, il y a régression. Un test dynamique peut être fonctionnel ou structurel [Beizer, 1990] :
– Un test fonctionnel ne considère que la spécification du programme sous test. Le programme est considéré comme enfermé dans une « boîte noire ».
– Un test structurel exploite lui la structure du programme sous test, qui est considéré comme étant dans une « boîte blanche ».
Le test peut également être utilisé pour s’assurer que la correction d’une faute ou l’ajout d’une nouvelle fonctionnalité au système sous test ne perturbe pas les fonctionnalités existantes. Il s’agit de test de non régression.
Model-Based Testing et génération automatique de l’oracle
La définition et l’application d’une suite de tests est une tâche coûteuse, c’est pourquoi il est intéressant de pouvoir automatiser tout ou partie de ce processus. L’une des solutions proposée est de générer les cas de test à partir d’une modélisation du système sous test, c’est le Model-Based Testing [Dalal et al., 1999]. La première étape de ce processus est la construction d’un modèle du système par le testeur. Ce modèle, généralement construit manuellement par le testeur à partir de la spécification, est une abstraction du système sous test. Après validation du modèle, les cas de test sont ensuite extraits de ce dernier selon un critère choisi par le testeur. Utting et al. [Utting et al., 2012] ont réalisé une étude des différentes approches et outils existants pour le Model-Based Testing. Ils classent notamment les approches étudiées selon la portée du modèle. Selon eux si le modèle spécifie les entrées possibles du système sous test, il peut aussi représenter le comportement attendu du programme sous test. Ainsi les cas de tests générés à partir de modèles ne spécifiant que les entrées ne contiennent que des données de test. Dans ce cas le testeur pourra utiliser des oracles basiques, le test passe si aucune exception n’est levée lors de l’exécution. S’il souhaite plus de précision, il lui faudra manuellement définir ses oracles, comme dans l’approche proposée par Dalal et al. [Dalal et al., 1999]. Dans les approches où le comportement du système est modélisé, il est possible d’extraire le résultat attendu pour l’exécution du système sur la donnée de test. Il est donc possible, dans ce cas, de générer automatiquement les oracles. C’est le cas de l’approche présentée par Jan Tretmans [Tretmans, 2008]. Il propose d’utiliser des systèmes d’états transitions pour modéliser le comportement du système. Pretschner et al. [Pretschner et al., 2005] proposent également de modéliser le comportement du système ; le modèle est transformé en un problème de satisfaction de contraintes pour la génération des tests. Ces approches permettent de générer automatiquement des cas de test (données et oracles) à partir d’un modèle représentant le comportement du système. Cependant, le modèle est généralement construit par le testeur à partir des spécifications du système sous test, comme pour l’oracle. Ces approches ne résolvent donc pas le problème de la création d’un oracle ; elles le déplacent. Pour générer automatiquement des oracles, le testeur devra d’abord construire le modèle de comportement.
Qualification d’oracles
À la différence des données de test, le sujet de la qualification d’oracles a peu été étudié. Un autre des défis mentionnés par Antonia Bertolino [Bertolino, 2007] concerne l’oracle. Ce défi consiste à définir des méthodes efficaces pour obtenir et automatiser des oracles. En l’état actuel des connaissances, comme nous l’avons mentionné dans la Section 2.1.2, la création d’oracles reste une tâche essentiellement manuelle. Il existe cependant des approches permettant de qualifier un ensemble d’oracles existant. Tout comme pour la qualification de données de test, la qualification d’oracles se fait principalement par une mesure de couverture ou l’analyse de mutation. Shuler et Zeller [Schuler and Zeller, 2011] proposent de qualifier des oracles pour du test unitaire à partir d’une mesure de couverture. Parmi les instructions qui ont une influence sur le résultat contrôlé par l’oracle, ils mesurent la proportion de celles réellement contrôlées par l’oracle. Ils proposent d’utiliser du program slicing pour identifier les instructions qui influencent le résultat contrôlé par l’oracle, puis parmi ces instructions ils mesurent la proportion réellement contrôlée par l’oracle. Selon eux, avec leur approche, le testeur ne chercherait pas à exécuter autant de code que possible mais à contrôler autant de résultats que possible. De cette manière il identifierait plus de fautes. Cette approche fournit également des indicateurs pour améliorer la qualité des oracles. Le testeur sait quelles instructions ne sont pas couvertes. Il doit donc améliorer ses oracles pour contrôler le résultat de ces instructions. La mise en œuvre de cette approche est spécifique au langage de programmation utilisé, comme pour toute approche utilisant la couverture de code. De plus, elle nous semble difficilement applicable dans le contexte qui nous intéresse, le test de transformations de modèles. Il est possible d’associer une instruction aux éléments du modèle de sortie sur lesquels elle a un impact, mais comment peut-on identifier quels éléments du modèle obtenu sont contrôlés par l’oracle ? En plus d’être utilisée pour qualifier des données de test, l’analyse de mutation permet également de qualifier des oracles. C’est ce que proposent Jézéquel et al. [Jézéquel et al., 2001]. Le score de mutation obtenu reflète la capacité des oracles à détecter des erreurs dans les modèles de sortie produits par les mutants. De nouveau, nous détaillons ci-dessous l’utilisation de l’analyse de mutation pour qualifier des oracles pour le test de transformations de modèle. Knauth et al. [Knauth et al., 2009] utilisent eux aussi l’analyse de mutation pour évaluer la qualité d’oracles à base de contrats. Staats et al. [Staats et al., 2012] considèrent eux aussi que la création d’un oracle reste un processus essentiellement manuel, réalisé par le testeur. Plutôt que de vouloir générer l’oracle d’un test, ils proposent d’aider le testeur en réduisant le nombre de variables desortie à contrôler. Ils utilisent pour cela l’analyse de mutation, ils considèrent uniquement les variables qui ont permis de tuer un mutant et les trient pour ne garder que les plus pertinentes.
Fonctionnement de la comparaison de modèles
Dans la comparaison de modèles trois phases sont distinguées, le calcul de la comparaison en elle-même, la manière dont le résultat de cette dernière sera représenté, et enfin la visualisation dudit résultat par l’utilisateur. Le calcul de la comparaison se décompose en deux phases distinctes. La première phase consiste à identifier les éléments communs aux deux modèles. Une fois ces points communs identifiés, la seconde phase s’attache à l’examen des différences. Förtsch et al. [Förtsch and Westfechtel, 2007] proposent une liste de propriétés permettant d’évaluer un outil de comparaison de modèles, sa précision, son niveau d’abstraction, son domaine, son indépendance vis-à-vis des outils utilisés pour la création des modèles, son indépendance par rapport aux précédentes versions des modèles, son efficacité, l’ergonomie de la partie visualisation, ainsi que sa complexité.
|
Table des matières
1 Introduction
1.1 Contexte et problématique
1.2 Cas d’étude
1.2.1 Mise à plat d’une machine à états UML
1.2.2 Diagramme d’activité UML vers CSP
2 État de l’art
2.1 Test logiciel
2.1.1 Processus du test
2.1.2 Oracle du test
2.1.3 Ingénierie des besoins
2.1.4 Qualité des tests
2.2 Ingénierie Dirigée par les Modèles (IDM)
2.2.1 Principes de l’IDM
2.2.2 Transformations de modèles
2.2.3 Outils dédiés à l’IDM
2.2.4 Comparaison de Modèles et IDM
2.3 Test de transformations de modèles
2.3.1 Sélection / génération de modèles de test
2.3.2 Oracle du test de transformations de modèles
2.3.3 Qualité des tests
2.3.4 Utilisation de la traçabilté pour le test de transformations de modèles
2.3.5 Vérification de transformations de modèles
2.4 Synthèse
3 Oracle partiel pour le test de transformations de modèles
3.1 Proposition d’une fonction d’oracle partielle
3.1.1 Donnée d’oracle partielle pour contrôler une partie d’un modèle de sortie
3.1.2 Avantages de notre approche et complémentarité avec les autres fonctions d’oracle
3.2 Mise en œuvre de l’approche proposée
3.2.1 Environnement technique
3.2.2 Traitement automatique des patterns
3.3 Validation expérimentale
3.3.1 Protocole d’expérimentation
3.3.2 Résultats obtenus
3.3.3 Validité des expériences : analyse critique
3.4 Conclusion
4 Qualification d’oracles pour le test de transformations de modèles
4.1 Couverture du méta-modèle de sortie et qualité des oracles
4.1.1 Proposition : qualification des oracles pour le test de transformations de modèles
4.1.2 Mesure de la couverture du méta-modèle de sortie
4.2 Mise en œuvre de notre proposition
4.2.1 Environnement technique
4.2.2 Couverture d’un méta-modèle par un ensemble de modèles
4.3 Validation expérimentale
4.3.1 Protocole d’expérimentation
4.3.2 Résultats et discussion
4.4 Perspectives : Amélioration de la qualité des oracles
4.4.1 Raisons d’une couverture incomplète
4.4.2 Rétroaction pour le testeur et aide au diagnostic
4.4.3 Limites de l’aide au diagnostic
4.5 Conclusion
5 Conclusion et perspectives
5.1 Contributions
5.1.1 Fonction d’oracle partielle pour le test de transformations de modèles
5.1.2 Qualification d’oracles pour le test de transformations de modèles
5.2 Perspectives
5.2.1 Approfondir notre évaluation de la qualité
5.2.2 Comparaison des fonctions d’oracles
5.2.3 Localisation des fautes dans la transformation sous test
A
A.1 Mise à plat d’une machine à état
A.1.1 Fragments de métamodèle
A.1.2 Patterns Incquery générés
A.2 UML vers CSP
A.2.1 Fragments de méta-modèle
A.2.2 Patterns Incquery générés
B
Bibliographie
Bibliographie
Liste des figures
Télécharger le rapport complet