THÈse présentée








télécharger 0.65 Mb.
titreTHÈse présentée
page3/28
date de publication09.06.2018
taille0.65 Mb.
typeThèse
ar.21-bal.com > droit > Thèse
1   2   3   4   5   6   7   8   9   ...   28

2
Etat de l’art


La programmation orientée objets a introduit de nouvelles structures qui doivent être prises en compte pour le test de logiciels. L’encapsulation des données dans des classes, l’héritage, le polymorphisme, font l’essentiel des constructions qui obligent à repenser le test de logiciels orientés objet(OO). De plus la construction des logiciels OO rend le découpage méthodologique des phases de test en unité-intégration-système inadapté dans la majeure partie des cas. Par exemple, certaines unités dans un système sont trop liées les une aux autres pour pouvoir être testées séparément, et on peut considérer la construction d’un système à objets comme une succession d’étapes d’intégration.

Depuis dix ans, de nombreux travaux se sont intéressés au test de logiciels OO, et récemment plusieurs ouvrages ont abordé ce sujet dans sa globalité [Siegel''96; Binder''99; McGregor''01]. Les premiers travaux ont consisté à appliquer les techniques du test de logiciels procéduraux au test de logiciels OO en n’apportant que quelques légères modifications (par exemple en prenant la classe comme unité de test et non plus la procédure/méthode). Avec l’apparition de standards méthodologiques (design by contract, rational process, catalysis…) ou de notation (UML) pour la construction de logiciels OO, la recherche sur le test de logiciels OO a pris en compte certaines spécificités de la programmation OO et des techniques originales sont apparues. Si ces travaux ont tenu compte très tôt du polymorphisme et de l’utilisation d’assertions/contrats pour le test, le choix de la notation UML (Unified Modeling Language) comme support de définition de test est relativement récent et l’analyse du processus même de conception OO en vue du test (patrons de conception, contrôle distribué) est quasiment inexistante.

Le but de ce chapitre est d’introduire le domaine du test, d’abord d’un point de vue général, puis de détailler les points particuliers correspondant aux trois contributions de cette thèse. Nous commençons par de brefs rappels sur l’activité du test de logiciel et le vocabulaire associé, puis nous présentons les travaux spécifiques au test de logiciels orientés objet, enfin nous terminons la première section avec la présentation d’une technique de validation de cas de test très largement utilisée au cours des travaux décrits dans cette thèse : l’analyse de mutation. Nous présentons ensuite quelques éléments de la notation UML utilisés pour le test, et continuons avec l’introduction d’une méthode pour la conception de composants logiciels fiables. Enfin, les trois dernières sections présentent des états de l’art détaillés relatifs aux trois points abordés dans la suite de cette thèse : la génération automatique de cas de test, l’impact de la conception par contrat pour le test et la testabilité des logiciels.

2.1Le test de logiciel


Dans cette première section, nous présentons l’approche générale pour le test de logiciels, puis nous détaillons les travaux particuliers au test de logiciels orientés objet. Enfin, nous introduisons l’analyse de mutation, une technique particulière pour la validation et la génération de cas de test qui est utilisée dans les chapitre 3 et 4.

2.1.1Le test classique


Le test de logiciel a pour but de détecter des erreurs dans un programme. Les termes de faute, erreur et défaillance ont été définis précisément dans [Laprie''95]. Une faute désigne la cause d’une erreur, une erreur est la partie du système qui est susceptible de déclencher une défaillance, et une défaillance correspond à un événement survenant lorsque le service délivré dévie de l’accomplissement de la fonction du système. Dans la suite de ce chapitre nous n’utiliserons que le terme erreur pour désigner le but du test.

Très schématiquement, le test dynamique de logiciel consiste à exécuter un programme avec un ensemble de données en entrée, et à comparer les résultats obtenus avec les résultats attendus. Si les résultats diffèrent, une erreur a été détectée, auquel cas, il faut localiser cette erreur et la corriger. Quand l’erreur est corrigée, il convient de retester le programme pour s’assurer de la correction et la non régression du programme. Enfin, pour être complète, l’activité de test implique de déterminer un « critère d’arrêt », qui spécifie quand le programme a été suffisamment testé.

A chacune de ces étapes pour le test correspond un terme particulier. Pour fixer la terminologie, et en s’inspirant de [Xanthakis''92], on considère que le programme testé est appelé programme sous test que les données de test désignent les données en entrée du programme sous test, et qu’un cas de test désigne le programme chargé d’exécuter le programme sous test avec une donnée de test particulière. Le cas de test se charge aussi de mettre le programme sous test dans un état approprié à l’exécution avec les données de test en entrée. Par exemple, pour tester le retour d’un livre dans une bibliothèque, il faut d’abord emprunter le livre. Au cours du chapitre 3, nous nous intéressons au problème de la génération automatique de données de test efficaces. L’efficacité de ces données est évaluée grâce à l’analyse de mutation qui est présentée dans la suite de ce chapitre.

Après l’exécution de chaque cas de test, il faut comparer le résultat obtenu avec le résultat attendu. Ce prédicat qui permet de déterminer si le résultat est incorrect est appelé un oracle ou une fonction d’oracle. Ce prédicat peut être explicite dans les cas de test, être obtenu indirectement par des assertions ou d’autres moyens (par exemple, le model-checking) ou, dans le cas le pire, être implicite et dépendre d’un verdict humain. Il est clair que l’oracle peut être plus ou moins spécifique à la donnée de test, et pourra ne pas détecter une erreur. Par contre, on attend de l’oracle d’être correct, c’est-à-dire de ne pas marquer comme erroné un comportement correct. L’oracle est donc le reflet, plus ou moins complet, et exécutable, de la spécification.

Lorsqu’un cas de test échoue, il faut localiser la source de la défaillance dans le programme pour pouvoir corriger la faute. Cette étape de localisation est appelée la phase de diagnostic. Lorsque le résultat obtenu est conforme à l’oracle, la dernière étape du test consiste à déterminer si les cas de test sont suffisant pour garantir la qualité du logiciel. Pour cela, il faut définir un critère de test ou critère d’arrêt et vérifier si les cas de test générés vérifient ou non ce critère. Les techniques de génération visent donc souvent à vérifier un critère d’arrêt particulier (plutôt qu’à chercher à détecter des erreurs). Pour générer des cas de test en fonction d’un critère de test, il est possible de définir des objectifs de test. Par exemple, si le critère exige l’exécution de toutes les instructions du programme au cours du test, « couvrir l’instruction 11 » est un objectif de test possible. Il faut ensuite écrire un cas de test qui vérifie cet objectif. Cette notion d’objectif de test s’applique non seulement aux aspects structurels (couverture de code), mais aussi aux aspects comportementaux (observation d’un certain échange de messages [Pickin''02]). Le chapitre 5 définit un critère de test pour les logiciels OO, et s’en sert comme support pour une analyse prédictive de la testabilité visant à estimer l’effort de test.

Les techniques pour la génération de cas de test sont de deux types : le test fonctionnel et le test structurel [Beizer''90]. Si le programme sous test est considéré comme une boîte noire (on ne tient pas compte de la structure interne du programme), on parle de test fonctionnel. Dans ce cas, la génération de cas de test se fait à partir d’une spécification la plus formelle possible du comportement du programme. Cette technique a pour avantage de générer des cas de test qui seront réutilisables même si l’implantation change ; elle ne garantit cependant pas que tout le programme ait été couvert par les cas de test.

Le test structurel s’appuie sur la structure interne du programme pour générer des cas de test. Les techniques de test structurel se basent généralement sur une représentation abstraite de cette structure interne, un modèle du programme, très souvent un graphe (graphe flot de contrôle [Beizer''90], graphe flot de données [Rapps''85]). Cette représentation permet de définir des critères de couverture indépendants d’un programme particulier : couverture de tous les arcs du graphe, tous les nœuds, tout ou partie des chemins… Le test structurel a pour avantage de permettre de valider les cas de test générés, en fonction de leur taux de couverture du critère.

Le test a lieu à différents moments dans le cycle de développement du logiciel, les différentes étapes sont traditionnellement les suivantes :

  • le test unitaire : une unité est la plus petite partie testable d'un programme, c'est souvent une procédure ou une classe dans les programmes à objet.

  • le test d'intégration : consiste à assembler plusieurs unités et à tester les erreurs liées aux interactions entre ces unités (et éventuellement à détecter les erreurs rémanentes non détectées au niveau unitaire).

  • le test système : teste le système dans sa totalité en intégrant tous les sous-groupes d'unités testés lors du test d'intégration. Il s’étend souvent à d’autres aspects tels que les performances, le test en charge…

2.1.2le test de logiciels orientés objet


Les mécanismes propres aux langages orientés objet et les méthodes d’analyse de conception et de développement associées, ont entraîné la nécessité de nouvelles techniques de test pour les logiciels fondés sur ces langages et méthodes. Une première modification a été le changement d’échelle pour le test unitaire. En effet, cette partie du test se concentrait sur les procédures dans le cadre des langages procéduraux, alors qu’elle s’intéresse à une classe dans un cadre orienté objet.

Il existe de nombreux travaux sur le test de classe, concernant les différents problèmes que sont la génération de données de test, les critères de couverture, la production d’un oracle, et l’écriture des drivers de test. En ce qui concerne les critères de couverture, [Harrold''94; Buy''00] étudient les critères flot de données et flot de contrôle pour le test d’une classe. Dans [Harrold''94], Harrold et al. proposent trois niveaux de granularité (intra-méthode, inter-méthode et intra-classe) pour l’analyse des flots de données dans une classe. Ils donnent l’algorithme pour construire le graphe et l’illustrent sur une classe. Buy et al. [Buy''00] repartent des travaux de Harrold et étendent l’approche à des techniques d’exécution symbolique et de déduction automatique, dans le but de générer automatiquement des séquences d’appels de méthodes pour tester une classe. La combinaison de ces trois techniques pour la génération de test est illustrée en détail sur un exemple. Les auteurs ont étudié, à la main, la faisabilité de l’approche sur plusieurs études de cas, et veulent maintenant développer les outils pour l’automatiser. Quant à McGregor [McGregor''01], il propose trois critères fondés sur la couverture de la machine à états associée à la classe, sur la résolution des paires de contraintes pré/post conditions, et la couverture de code.

Pour la génération des données de test plusieurs solutions ont été proposées. Dans [Buy''00], les auteurs s’intéressent à l’exécution symbolique, McGregor [McGregor''01], propose d’utiliser les contraintes les pré et post conditions exprimées en OCL pour déduire les données de test pertinents automatiquement. Dans la suite de cette thèse nous étudions deux algorithmes évolutionnistes pour la génération automatique de données de test pour un composant orienté objet ces travaux ont été publiés dans [Baudry''00b; Baudry''02d]). Les travaux qui se sont intéressés à la production de l’oracle pour le test unitaire de classes, sont tous fondés sur la spécification de post-conditions pour les méthodes de la classe [Edwards''01; Cheon''02; Fenkam''02]. Ces travaux sont détaillés dans la section 2.5 sur l’utilisation des contrats pour le test.

Enfin, pour l’organisation des cas de tests, et la production de drivers de test, [Jézéquel''01] propose d’embarquer les cas de test dans la classe sous test. C’est l’idée de classe auto-testable sur laquelle s’appuie en partie cette thèse, et qui sera détaillée dans la section suivante. D’autre part, la méthode dite d’eXtremeProgramming proposée par Kent Beck [Beck''99] préconise une utilisation importante du test unitaire. Dans ce cadre, une famille de frameworks a été développée pour le test unitaire de classes dans différents langages orientés objet : Junit pour Java, EiffelUnit pour Eiffel, Nunit pour la plate-forme .NET [Beck''01; Craig''02] … Une bonne structuration des cas de test unitaires offre aussi l’avantage d’une réutilisation facile au moment du test de non régression.

Un autre point particulier pour le test de logiciels orientés objet, est le test d’intégration. En effet, les systèmes OO peuvent être vus comme l’assemblage de plusieurs unités offrant chacune une interface aux autres unités dans le système. Si une unité utilise les services offerts par une autre unité, la première est appelée unité cliente et la seconde unité serveur. Le test d’intégration consiste alors à vérifier que les unités clientes utilisent les unités serveur en conformité avec l’interface offerte par l’unité serveur. Des critères de couverture pour le test des interactions entre classes, ou entre composants dans un système ont été proposés dans [Chen''99; Alexander''00; Alexander''02a; Baudry''02a; Martena''02; Wu''03]. Tous ces critères visent à couvrir certains types de relations entre des classes, dans le but de découvrir des erreurs dues à une mauvaise utilisation d’une unité serveur par une unité cliente. De plus, dans [Wu''03] les auteurs décrivent les éléments de spécification UML nécessaires au test d’intégration de composants. Wu et al. décrivent les descriptions comportementales minimum pour pouvoir tester les interactions avec un composant dont le code source n’est pas disponible. Les auteurs de [Hoijin''98; Ghosh''00] proposent d’utiliser des techniques de mutation sur les interfaces des composants à intégrer pour valider le test d’intégration.

Un problème complémentaire au test d’intégration, est d’obtenir un ordre d’intégration des composants d’un système qui soit efficace. En effet, les classes dans un système orienté objet sont interdépendantes, et il peut apparaître des cycles dans ces relations de dépendance. Dans ce cas, il faut simuler le comportement d’une classe pour pouvoir en tester une autre. Or, l’écriture d’un simulateur peut être très coûteuse, et plusieurs travaux ont étudié des techniques pour trouver un ordre d’intégration qui minimise la génération de simulateurs [Kung''96; Tai''99; Labiche''00; Briand''01; Hanh''02]. Soundarajan et al. présentent une étude en rapport avec l’ordre d’intégration dans [Soundarajan''01]. Cet article est fondé sur le fait que l’héritage permet de développer des systèmes de manière incrémental, et que les cas de test pour les sous-classes doivent être développés aussi de manière incrémental à partir des cas de test pour les super-classes.

Pour le test système, plusieurs méthodes pour la production de cas de test ont été proposées [Briand''02a; Nebut''02; Pickin''02]. Ces trois méthodes sont fondées sur la conjonction d’informations obtenues à partir de plusieurs vues UML du système. Les auteurs de [Briand''02a] proposent d’utiliser les cas d’utilisation et les diagrammes de collaboration associés pour ordonner les cas de test dans une suite de tests système, puis d’utiliser les diagrammes de séquence ainsi que les pré et post conditions sur les méthodes pour dériver des cas de test. Dans [Nebut''02], l’idée est de définir des patrons de test abstraits sous forme de diagrammes de séquence génériques, qui peuvent être instanciés vis-à-vis d’un diagramme de classe particulier. Enfin [Pickin''02] s’appuie sur la machine à états associée à chaque classe du système, et une description sous forme de diagramme de séquence d’un objectif de test, pour générer un cas de test correspondant à l’objectif.

Enfin, certains travaux se concentrent sur certaines vues d’UML pour le test de logiciels OO. De nombreux travaux traitent de la génération de test à partir des machines à état, avec des diagrammes d’états UML [Kim''99; Offutt''99; Bogdanov''01; Chevalley''01b; Chevalley''01a; Antoniol''02]. Certains de ces travaux proposent des critères de couverture des diagrammes d’états, comme la couverture de tous les états, couverture des transitions ou encore couverture des paires de transition. Ces critères permettent alors la génération automatique de cas de test à partir d’un tel diagramme. Les auteurs de [Abdurazik''00] utilisent le diagramme de collaborations pour tester certaines propriétés des routines décrites à l’aide de ces diagrammes.

Dans la section suivante nous nous concentrons sur une technique pour évaluer la qualité des cas de test pour un composant : l’analyse de mutation. Cette méthode qui fut d’abord proposée pour les programmes procéduraux, a ensuite été adaptée pour le test de logiciels orientés objet.

2.1.3L’analyse de mutation


L’analyse de mutation est une technique pour la validation de données de test fondée sur l’injection de fautes. L’insertion systématique d’erreurs dans un logiciel est souvent utilisée pour la validation expérimentale de techniques de test. La connaissance du nombre et de l’emplacement précis des erreurs dans le logiciel permet en effet de contrôler l’efficacité de techniques ou méthodes de test ou de diagnostic [Jones''02].

L’analyse de mutation est une technique proposée par DeMillo [DeMillo''78] et peut être utilisée de deux façons : évaluer la qualité d’un ensemble de cas de test, guider 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, et à exécuter un ensemble de cas de test sur chacun de ces programmes erronés. Un mutant est une copie exacte du programme sous test dans lequel une seule erreur simple a été injectée. En pratique, l’ensemble des mutants est créé en soumettant le programme à des opérateurs de mutation qui correspondent à différents types d’erreur (changement de signe des constantes, changement de sens d’une inégalité…).

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ée (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 à couvrir les zones où se situent ces erreurs, et à générer des données de test spécifiques pour couvrir les cas particuliers. Un vocabulaire particulier est associé à cette approche : si un cas de test peut détecter un mutant, on dit qu’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 : l’hypothèse du programmeur compétent, et l’effet de couplage. La première hypothèse met en avant le fait qu’un programmeur compétent écrit des programmes « presque » corrects. Autrement dit, un programme écrit par tel programmeur est sûrement incorrect, mais il est « proche » de la version correcte (il suffit de modifications mineures pour le corriger). L’hypothèse sur l’effet de couplage dit que si un ensemble de tests peut détecter les fautes simples dans un programme, alors il pourra détecter des fautes plus complexes. Même si les notions de faute simple et faute complexe ne sont pas formalisées, Offutt propose la définition suivante dans [Offutt''92]:

Faut simple, faute complexe. Une faute simple, est une faute qui peut être corrigée en ne modifiant qu’une seule instruction dans le code source. Une faute complexe est une faute qui ne peut pas être corrigée en ne modifiant qu’une seule instruction.

Dans ce même article, Offutt étudie l’injection de plusieurs erreurs. Il exécute ensuite les cas de test efficaces avec mutants ne comportant qu’une seule erreur sur les mutants comportant plusieurs erreurs. Ces expériences montrent que des cas de test détectant des erreurs simples détectent aussi des erreurs plus complexes issues de la combinaison de plusieurs erreurs simples, ce qui tend à valider l’hypothèse sur l’effet de couplage.

Offutt étudie aussi l’efficacité de l’analyse de mutation en la comparant à des critères flot de donnée dans [Offutt''96a]. Dans cet article, Offut compare les cas de test générés avec les deux techniques de deux manières : il regarde si les cas de test générés pour un critère peuvent satisfaire l’autre critère, et il compare le pouvoir de détection d’erreur de chacun. Il conclut que l’effort nécessaire pour la génération est identique dans les deux cas, mais que les cas de test générés par mutation satisfont plus facilement les critères flot de donnée, et découvrent plus d’erreurs.

Les erreurs injectées pour l’analyse de mutation correspondent à différents types d’erreurs de programmation. Ces types ont été isolés en observant les pratiques des programmeurs et en en analysant les erreurs détectées lors du test. De nombreux opérateurs ont été proposés pour les langages procéduraux, et Offutt étudie le sous-ensemble suffisant dans [Offutt''96b].

La génération de cas de test fondée sur l’analyse de mutation est fastidieuse, puisque tant qu’une proportion importante de mutants n’est pas détectée par les cas de test, il faut regarder pourquoi ils ne sont pas détectés, puis produire un cas de test visant à détecter chacun d’entre eux. Dans [DeMillo''91], les auteurs donnent une technique pour la génération automatique de cas de test fondée sur l’analyse de mutation. L’idée qu’ils développent dans cet article, et qu’ils ont affinée par la suite, est de fixer comme objectif de test l’endroit du programme où se trouve l’erreur, et de remonter toutes les contraintes sur les données entre cet endroit et l’entrée du programme. Ils peuvent ainsi générer une donnée de test qui atteint l’erreur et permet de détecter le mutant. Un outil pour le langage Fortran a été développé [DeMillo''93].

L’analyse de mutation a d’abord été conçue pour le test de logiciels procéduraux, mais depuis plusieurs années, des travaux se sont intéressés à cette technique dans le cadre de logiciels OO [Hoijin''98; Ghosh''00; Chevalley''01c; Kim''01; Alexander''02b; Ma''02]. Certains proposent de nouveaux opérateurs de mutation fondés sur les mécanismes spécifiques aux langages orientés objet [Chevalley''01c; Kim''01; Alexander''02b; Ma''02], alors que d’autres utilisent cette technique pour valider des cas de test d’intégration [Hoijin''98; Ghosh''00] ou unitaires [Baudry''00b]. Enfin, dans [Chevalley''01c] 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.

2.1.4Conclusion


Le test est une technique importante pour la validation de logiciels, et il existe de nombreux travaux qui s’intéressent aux différents aspects que recouvre cette activité. Dans cette section, nous avons présenté les principes et techniques principaux pour le test et avons introduit les différentes activités existantes autour du test de logiciels orientés objet. Enfin, nous avons introduit l’analyse de mutation qui est largement utilisée dans la suite de cette thèse pour évaluer la qualité des cas de test pour un programme.

Les deux sections suivantes illustrent certaines spécificités de la conception de logiciels orienté objet. Nous résumons tout d’abord quelques éléments de la notation UML qui seront nécessaires dans la suite pour illustrer certaines spécificités des programmes orientés objet. Ensuite, la section 2.5 détaille la méthode de conception par contrat, ainsi qu’une méthode plus générale pour la conception de composants logiciels fiables.
1   2   3   4   5   6   7   8   9   ...   28

similaire:

THÈse présentée iconThèse Présentée à la Faculté de Pharmacie de Montpellier

THÈse présentée iconThèse présentée pour l’obtention du grade de Docteur

THÈse présentée iconThèse soutenue publiquement par Sang-Ha suh le 10 Juillet 2006
«avec projection», de cette thèse aux membres du Conseil scientifique et à leurs expliquer pourquoi cette thèse ne devait pas être...

THÈse présentée iconThèse soutenue publiquement par Sang-Ha S. le 10 Juillet 2006 Le...
«avec projection», de cette thèse aux membres du Conseil scientifique et à leurs expliquer pourquoi cette thèse ne méritait pas d’être...

THÈse présentée iconQuestionnaire sur les Cathédrales Quelle est le nom de la cathédrale...

THÈse présentée iconCommuniqué de presse
«L’homme et la matièRE» de Don Darby que l’année prenait son envol. L’exposition est présentée à la Salle Principale du cne jusqu’au...

THÈse présentée iconCatégorie de Grand Prix présentée (cocher la case correspondante)
«Les Grands Prix simi immobilier de Bureaux» du 17 juin au Vendredi 7 octobre 2016

THÈse présentée iconThèse

THÈse présentée iconCommuniqué de presse
«Komuna Fundamento» présentée lors de la xiiie exposition internationale d’architecture «Common Ground» qui se déroule dans le cadre...

THÈse présentée iconAutomne 2013 plan de cours
«Unified», combinée à l'apprentissage du langage uml, est présentée et mise en pratique dans un projet de conception et d'implantation...








Tous droits réservés. Copyright © 2016
contacts
ar.21-bal.com