Rapport de thèse de doctorat - Irisa
Soutenue le 24 juin 2003 devant la commission d'Examen ..... Quand l'erreur est
corrigée, il convient de retester le programme pour s'assurer de la ...... Par
exemple, pour l'étude de cas sur un parseur C#, un cas de test est un fichier
source ...
part of the document
té dévaluer ce travail, et dont les remarques furent grandement appréciées.
Je remercie également Lionel Briand, Yvan Labiche, et Gerson Sunyé pour les échanges enrichissants que nous avons eus au cours de ces travaux.
Je souhaiterais aussi remercier tous ceux qui font que lIRISA est un endroit où jai particulièrement apprécié de travailler. Franck pour le débat, la dynamique de win, la relecture et surtout pour toutes les choses extrêmement utiles quil a réalisées. Clémentine parce quil est bene de travailler avec quelquun qui parle italien, Tewfik qui voit ce que je veux dire, Loïc pour avoir supporté les crises de guerre des étoiles et tout le monde dans léquipe Triskell. Merci aussi à Christelle, Marie-Noëlle, Nadège, Angélique, et aux filles de la cafet.
Je remercie Yannic Le Flem davoir dessiné laffiche de thèse sans oublier le petit booléen vert pour Sandrine et Isabelle pour avoir su synthétiser cette thèse de façon remarquable.
Enfin, je remercie tous ceux qui mont permis de ne pas basculer du côté obscur de la thèse en faisant de la vie ce quelle ne doit jamais cesser dêtre : passionnée. Je remercie tout dabord Sandrine, pour la correspondance mythique qui a accompagné la rédaction de ce mémoire. Je remercie Céline, Corentin, Jérôme, Antoine, Anne-Gaëlle, Nolwenn, Johanne, François et Cécile davoir partagé ma vie au cours de ces trois années, parce que je crois à la cohérence de tout ça, et ce travail existe aussi grâce à eux. Je remercie Jean-Louis Sechet, Mathieu Saglio, Kerwin Roland, Nusch Werchowska, John Cage, John Coltrane, Cecil Taylor, Olivier Messiaen et Gÿorgi Ligeti dont la musique ma toujours accompagné et aidé à avancer.
Je remercie mon grand-père Lou et mes parents, pour mavoir soutenu tout au long de cette thèse, et pour avoir insufflé la vie aux cas de test.
Extrait du Regard de lÉglise damour, n°20 des Vingt Regards sur lEnfant Jésus. Olivier Messiaen, 1947.
TOC \o "1-3" \h \z HYPERLINK \l "_Toc44995386" 1 Introduction PAGEREF _Toc44995386 \h 1
HYPERLINK \l "_Toc44995387" 2 Etat de lart PAGEREF _Toc44995387 \h 5
HYPERLINK \l "_Toc44995388" 2.1 Le test de logiciel PAGEREF _Toc44995388 \h 6
HYPERLINK \l "_Toc44995389" 2.1.1 Le test classique PAGEREF _Toc44995389 \h 6
HYPERLINK \l "_Toc44995390" 2.1.2 le test de logiciels orientés objet PAGEREF _Toc44995390 \h 8
HYPERLINK \l "_Toc44995391" 2.1.3 Lanalyse de mutation PAGEREF _Toc44995391 \h 10
HYPERLINK \l "_Toc44995392" 2.1.4 Conclusion PAGEREF _Toc44995392 \h 12
HYPERLINK \l "_Toc44995393" 2.2 Éléments de la notation UML pour le test de logiciels OO PAGEREF _Toc44995393 \h 13
HYPERLINK \l "_Toc44995394" 2.2.1 Diagramme de classes PAGEREF _Toc44995394 \h 13
HYPERLINK \l "_Toc44995395" 2.2.2 Vues dynamiques : diagrammes de séquence et détats PAGEREF _Toc44995395 \h 15
HYPERLINK \l "_Toc44995396" 2.3 Méthodologie pour une conception testable PAGEREF _Toc44995396 \h 16
HYPERLINK \l "_Toc44995397" 2.3.1 La conception par contrat PAGEREF _Toc44995397 \h 17
HYPERLINK \l "_Toc44995398" 2.3.2 Les contrats et UML : le langage OCL PAGEREF _Toc44995398 \h 18
HYPERLINK \l "_Toc44995399" 2.3.3 Les composants autotestables PAGEREF _Toc44995399 \h 19
HYPERLINK \l "_Toc44995400" 2.3.4 Conclusion PAGEREF _Toc44995400 \h 21
HYPERLINK \l "_Toc44995401" 2.4 La génération automatique de cas de test PAGEREF _Toc44995401 \h 22
HYPERLINK \l "_Toc44995402" 2.4.1 Techniques fonctionnelles PAGEREF _Toc44995402 \h 22
HYPERLINK \l "_Toc44995403" 2.4.2 Techniques structurelles PAGEREF _Toc44995403 \h 23
HYPERLINK \l "_Toc44995404" 2.5 Conception par contrat et test PAGEREF _Toc44995404 \h 25
HYPERLINK \l "_Toc44995405" 2.6 Testabilité et mesures pour les logiciels OO PAGEREF _Toc44995405 \h 27
HYPERLINK \l "_Toc44995406" 2.7 Conclusion PAGEREF _Toc44995406 \h 29
HYPERLINK \l "_Toc44995407" 3 Génération automatique de cas de test pour un composant PAGEREF _Toc44995407 \h 31
HYPERLINK \l "_Toc44995408" 3.1 Adaptation de lanalyse de mutation pour la qualification de composants PAGEREF _Toc44995408 \h 32
HYPERLINK \l "_Toc44995409" 3.1.1 Le processus global PAGEREF _Toc44995409 \h 33
HYPERLINK \l "_Toc44995410" 3.1.2 Loracle pour lanalyse de mutation PAGEREF _Toc44995410 \h 34
HYPERLINK \l "_Toc44995411" 3.1.3 Mutants équivalents PAGEREF _Toc44995411 \h 36
HYPERLINK \l "_Toc44995412" 3.1.4 Opérateurs de mutation utilisés pour la qualification des composants PAGEREF _Toc44995412 \h 37
HYPERLINK \l "_Toc44995413" 3.1.5 Outils PAGEREF _Toc44995413 \h 38
HYPERLINK \l "_Toc44995414" 3.1.6 Analyse de mutation pour une classe ou un système PAGEREF _Toc44995414 \h 39
HYPERLINK \l "_Toc44995415" 3.1.7 Optimisation automatique et analyse de mutation PAGEREF _Toc44995415 \h 41
HYPERLINK \l "_Toc44995416" 3.2 Algorithme génétique pour loptimisation de cas de test PAGEREF _Toc44995416 \h 42
HYPERLINK \l "_Toc44995417" 3.2.1 Les algorithmes génétiques PAGEREF _Toc44995417 \h 42
HYPERLINK \l "_Toc44995418" 3.2.2 Texte de lalgorithme PAGEREF _Toc44995418 \h 44
HYPERLINK \l "_Toc44995419" 3.2.3 Le problème de loptimisation de cas de test PAGEREF _Toc44995419 \h 46
HYPERLINK \l "_Toc44995420" 3.3 Etudes de cas pour un algorithme génétique PAGEREF _Toc44995420 \h 51
HYPERLINK \l "_Toc44995421" 3.3.1 Optimisation de cas de test unitaires : un exemple en Eiffel PAGEREF _Toc44995421 \h 51
HYPERLINK \l "_Toc44995422" 3.3.2 Optimisation de cas de test système : lexemple dun composant .NET PAGEREF _Toc44995422 \h 53
HYPERLINK \l "_Toc44995423" 3.3.3 Résultats et remise en cause du modèle génétique PAGEREF _Toc44995423 \h 54
HYPERLINK \l "_Toc44995424" 3.4 Une approche adaptative : un « algorithme bactériologique » PAGEREF _Toc44995424 \h 57
HYPERLINK \l "_Toc44995425" 3.4.1 Le modèle bactériologique PAGEREF _Toc44995425 \h 57
HYPERLINK \l "_Toc44995426" 3.4.2 De nouveaux résultats PAGEREF _Toc44995426 \h 61
HYPERLINK \l "_Toc44995427" 3.4.3 Discussion et validation du modèle bactériologique PAGEREF _Toc44995427 \h 62
HYPERLINK \l "_Toc44995428" 3.5 Paramétrage des modèles PAGEREF _Toc44995428 \h 64
HYPERLINK \l "_Toc44995429" 3.5.1 Paramétrage du modèle bactériologique PAGEREF _Toc44995429 \h 64
HYPERLINK \l "_Toc44995430" 3.5.2 Recherche dun algorithme intermédiaire PAGEREF _Toc44995430 \h 66
HYPERLINK \l "_Toc44995431" 3.6 Conclusion PAGEREF _Toc44995431 \h 68
HYPERLINK \l "_Toc44995432" 4 Robustesse et diagnosabilité : impact de la conception par contrat sur un assemblage de composants PAGEREF _Toc44995432 \h 69
HYPERLINK \l "_Toc44995433" 4.1 Conception par contrat et élaboration dune mesure PAGEREF _Toc44995433 \h 70
HYPERLINK \l "_Toc44995434" 4.1.1 Les contrats pour la robustesse et la diagnosabilité PAGEREF _Toc44995434 \h 70
HYPERLINK \l "_Toc44995435" 4.1.2 Elaboration dune mesure PAGEREF _Toc44995435 \h 71
HYPERLINK \l "_Toc44995436" 4.2 Mesure de la robustesse PAGEREF _Toc44995436 \h 72
HYPERLINK \l "_Toc44995437" 4.2.1 Définitions PAGEREF _Toc44995437 \h 72
HYPERLINK \l "_Toc44995438" 4.2.2 Axiomatisation PAGEREF _Toc44995438 \h 74
HYPERLINK \l "_Toc44995439" 4.2.3 Hypothèses et modèle mathématique PAGEREF _Toc44995439 \h 76
HYPERLINK \l "_Toc44995440" 4.2.4 Démonstration des axiomes PAGEREF _Toc44995440 \h 77
HYPERLINK \l "_Toc44995441" 4.2.5 Expériences pour paramétrer le modèle PAGEREF _Toc44995441 \h 82
HYPERLINK \l "_Toc44995442" 4.2.6 Application de la mesure sur trois systèmes réels et résultats PAGEREF _Toc44995442 \h 87
HYPERLINK \l "_Toc44995443" 4.2.7 Critique du modèle de mesure PAGEREF _Toc44995443 \h 89
HYPERLINK \l "_Toc44995444" 4.3 Une mesure de la diagnosabilité PAGEREF _Toc44995444 \h 91
HYPERLINK \l "_Toc44995445" 4.3.1 Analyse du problème PAGEREF _Toc44995445 \h 91
HYPERLINK \l "_Toc44995446" 4.3.2 Définitions PAGEREF _Toc44995446 \h 93
HYPERLINK \l "_Toc44995447" 4.3.3 La mesure de diagnosabilité PAGEREF _Toc44995447 \h 94
HYPERLINK \l "_Toc44995448" 4.3.4 Résultats et conclusions PAGEREF _Toc44995448 \h 99
HYPERLINK \l "_Toc44995449" 4.4 Conclusion PAGEREF _Toc44995449 \h 99
HYPERLINK \l "_Toc44995450" 5 Anti-patterns de testabilité dans un assemblage de composants PAGEREF _Toc44995450 \h 101
HYPERLINK \l "_Toc44995451" 5.1 Présentation de la problématique PAGEREF _Toc44995451 \h 101
HYPERLINK \l "_Toc44995452" 5.2 Testabilité dune architecture OO: définitions et méthodologie PAGEREF _Toc44995452 \h 104
HYPERLINK \l "_Toc44995453" 5.2.1 Testabilité PAGEREF _Toc44995453 \h 104
HYPERLINK \l "_Toc44995454" 5.2.2 Une méthodologie pour la conception de systèmes testables PAGEREF _Toc44995454 \h 105
HYPERLINK \l "_Toc44995455" 5.2.3 Exemple PAGEREF _Toc44995455 \h 106
HYPERLINK \l "_Toc44995456" 5.3 Critère de test et anti-patterns pour les architectures OO PAGEREF _Toc44995456 \h 107
HYPERLINK \l "_Toc44995457" 5.3.1 Analyse informelle des anti-patterns de testabilité PAGEREF _Toc44995457 \h 107
HYPERLINK \l "_Toc44995458" 5.3.2 Complexité de l'héritage PAGEREF _Toc44995458 \h 109
HYPERLINK \l "_Toc44995459" 5.3.3 Critère de test pour des systèmes OO PAGEREF _Toc44995459 \h 110
HYPERLINK \l "_Toc44995460" 5.3.4 Exemple pour la génération de test PAGEREF _Toc44995460 \h 113
HYPERLINK \l "_Toc44995461" 5.4 Modélisation des anti-patterns PAGEREF _Toc44995461 \h 116
HYPERLINK \l "_Toc44995462" 5.4.1 Construction dun graphe à partir d'un diagramme de classes d'UML PAGEREF _Toc44995462 \h 116
HYPERLINK \l "_Toc44995463" 5.4.2 Détecter des anti-patterns à partir du GDC PAGEREF _Toc44995463 \h 118
HYPERLINK \l "_Toc44995464" 5.4.3 Complexité des anti-patterns PAGEREF _Toc44995464 \h 119
HYPERLINK \l "_Toc44995465" 5.4.4 Mesure de la complexité des anti-patterns : système de gestion de livres PAGEREF _Toc44995465 \h 121
HYPERLINK \l "_Toc44995466" 5.5 Amélioration de la testabilité du modèle PAGEREF _Toc44995466 \h 122
HYPERLINK \l "_Toc44995467" 5.6 Exemples d'application PAGEREF _Toc44995467 \h 124
HYPERLINK \l "_Toc44995468" 5.6.1 Le gestionnaire de livres PAGEREF _Toc44995468 \h 124
HYPERLINK \l "_Toc44995469" 5.6.2 Serveur De Réunion Virtuelle PAGEREF _Toc44995469 \h 125
HYPERLINK \l "_Toc44995470" 5.6.3 Une architecture de compilateur PAGEREF _Toc44995470 \h 127
HYPERLINK \l "_Toc44995471" 5.7 Design Patterns pour la testabilité du modèle PAGEREF _Toc44995471 \h 129
HYPERLINK \l "_Toc44995472" 5.7.1 Concevoir par cristallisation de patterns PAGEREF _Toc44995472 \h 129
HYPERLINK \l "_Toc44995473" 5.7.2 Application du design pattern State pour améliorer la testabilité PAGEREF _Toc44995473 \h 131
HYPERLINK \l "_Toc44995474" 5.8 Analyse de testabilité des design patterns PAGEREF _Toc44995474 \h 134
HYPERLINK \l "_Toc44995475" 5.8.1 Analyse de testabilité de State PAGEREF _Toc44995475 \h 134
HYPERLINK \l "_Toc44995476" 5.8.2 Analyse de testabilité d'Abstract Factory PAGEREF _Toc44995476 \h 135
HYPERLINK \l "_Toc44995477" 5.8.3 Définir des contraintes de testabilité au niveau méta PAGEREF _Toc44995477 \h 136
HYPERLINK \l "_Toc44995478" 5.8.4 La grille de testabilité des design patterns PAGEREF _Toc44995478 \h 138
HYPERLINK \l "_Toc44995479" 5.9 Conclusion PAGEREF _Toc44995479 \h 140
HYPERLINK \l "_Toc44995480" 6 Conclusions et perspectives PAGEREF _Toc44995480 \h 141
HYPERLINK \l "_Toc44995481" 6.1 Contributions majeures PAGEREF _Toc44995481 \h 141
HYPERLINK \l "_Toc44995482" 6.1.1 Validation de composants PAGEREF _Toc44995482 \h 141
HYPERLINK \l "_Toc44995483" 6.1.2 Testabilité dun assemblage de composants PAGEREF _Toc44995483 \h 143
HYPERLINK \l "_Toc44995484" 6.2 Perspectives PAGEREF _Toc44995484 \h 143
HYPERLINK \l "_Toc44995485" 6.2.1 La génération de test pour le diagnostic PAGEREF _Toc44995485 \h 143
HYPERLINK \l "_Toc44995486" 6.2.2 Les contrats comme oracle de test PAGEREF _Toc44995486 \h 144
HYPERLINK \l "_Toc44995487" 6.2.3 Transformation de modèles pour la testabilité PAGEREF _Toc44995487 \h 144
HYPERLINK \l "_Toc44995488" 6.2.4 Design patterns et test PAGEREF _Toc44995488 \h 144
HYPERLINK \l "_Toc44995489" Annexes PAGEREF _Toc44995489 \h 145
HYPERLINK \l "_Toc44995490" Annexe A Répartition des contrats dans un système OO PAGEREF _Toc44995490 \h 147
HYPERLINK \l "_Toc44995491" Annexe B Interactions dobjets pour le gestionnaire de livres PAGEREF _Toc44995491 \h 151
HYPERLINK \l "_Toc44995492" AU1 PAGEREF _Toc44995492 \h 151
HYPERLINK \l "_Toc44995493" AU2 PAGEREF _Toc44995493 \h 152
HYPERLINK \l "_Toc44995494" IC(BookEvent, Book) PAGEREF _Toc44995494 \h 153
HYPERLINK \l "_Toc44995495" Glossaire PAGEREF _Toc44995495 \h 155
HYPERLINK \l "_Toc44995496" Bibliographie PAGEREF _Toc44995496 \h 159
Introduction
Le test de logiciel apparaît aujourdhui comme le moyen principal pour la validation du fonctionnement dun programme ADDIN EN.CITE Binder199941Binder, R. V.1999Testing Object-Oriented Systems: Models, Patterns and ToolsAddison-WesleyISBN 0-201-80938-9Beizer19901791Beizer, Boris1990Software Testing TechniquesVan Norstrand Reinhold0-442-20672-0[Beizer''90; Binder''99]. Il a pour objectif dexaminer ou dexécuter un programme dans le but dy révéler des erreurs. Il est souvent défini comme le moyen par lequel on sassure quune implantation est conforme à ce qui a été spécifié. Lactivité de test est omniprésente tout au long du cycle de vie du logiciel, et différentes techniques permettent de valider les différentes étapes du développement.
Par ailleurs, lindustrie du logiciel produit aujourdhui des systèmes de plus en plus complexes ; il devient donc crucial de factoriser le savoir-faire et les produits. Le savoir-faire est réutilisé sous la forme de processus et de méthodologies pour la gestion de projets logiciels ou par la mutualisation de solutions éprouvées au niveau de la conception grâce, en particulier, à la notion de design patterns ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95]. Le développement de canevas dapplication (« frameworks ») et de composants logiciels constitue une des réponses à la réutilisation de certaines parties de limplantation.
La programmation orientée objets (OO) offre une solution élégante au développement de composants réutilisables comme unité de déploiement ADDIN EN.CITE Szyperski19981301Szyperski, Clemens1998Component Software: Beyond Object-Oriented ProgrammingNew-York, N.-Y., USAACM Press and Addison Wesley[Szyperski''98]. Elle offre en particulier des possibilités d'encapsulation et de masquage d'information qui permettent d'établir une analogie avec les composants matériels, et la notion d'adaptabilité qui permet de les adapter souplement. Ce type de programmation est maintenant largement utilisé pour lanalyse, la conception et la réalisation de grands systèmes dinformation. Ladoption des langages orientés objets sest accompagnée de lémergence dUML (Unified Modeling Language) comme langage standard de modélisation. Ce langage permet la description des différents aspects du logiciel, de la description structurelle et du comportement dynamique du programme jusquà la définition des cas de test ou de lenvironnement de déploiement.
Ladoption généralisée du paradigme objet, ainsi que le changement déchelle pour le développement de logiciel fait émerger la nécessité de méthodes adaptées pour le test ADDIN EN.CITE Binder199941Binder, R. V.1999Testing Object-Oriented Systems: Models, Patterns and ToolsAddison-WesleyISBN 0-201-80938-9[Binder''99]. En effet, le besoin de fiabilité et de robustesse pour les composants est dautant plus grand que ceux-ci vont être réutilisés dans de nombreux contextes différents. Lencapsulation des données dans des classes, la répartition du contrôle à travers le système, ou la liaison dynamique sont autant de spécificités qui doivent être prises en compte par les techniques et méthodes de test proposées. Par ailleurs, il semble intéressant dappuyer ces méthodes sur UML comme langage de modélisation des logiciels.
Comme nous le verrons par la suite, il existe de très nombreux travaux qui se sont intéressés au test de logiciels orientés objet, allant du test dunités indépendantes jusquaux processus génériques pour la validation de systèmes complexes. Ceci a donné lieu, par exemple, à une standardisation du test de classe, à travers la popularisation dune famille de frameworks pour divers langages orientés objet appelée Xunit ADDIN EN.CITE Craig20026316Craig, Philip2002NUnit2002Mayhttp://nunit.sourceforge.net/Beck20014816Beck, KentGamma, E.2001JUnit2002http://www.junit.org/index.htm[Beck''01; Craig''02]. Ces frameworks prennent en compte la structure particulière des programmes OO pour lécriture, lexécution et la collecte des résultats du test pour une classe. Par ailleurs, de la même manière que les design patterns et les composants permettent la réutilisation darchitectures et de code, il existe des patterns de test qui correspondent à des définitions abstraites de plans de test ou de cas de test qui peuvent être réutilisées pour différentes applications.
Les travaux présentés dans cette thèse sarticulent autour de trois contributions majeures qui sinscrivent dans le cadre global du test de logiciels orienté objet pour le développement de composants logiciels fiables.
Considérant que le test constitue le moyen principal pour lévaluation de la fiabilité dun composant, la génération de cas de test efficaces est un problème important. Or, sil est possible de générer rapidement des cas de test couvrant les utilisations normales dun programme, lamélioration de cet ensemble pour quil soit efficace dans tous les cas est très coûteuse à la fois en temps et en effort. Loptimisation automatique dun ensemble de cas de test est donc la première contribution de cette thèse. Ce travail a consisté à étudier un algorithme génétique, et à définir une nouvelle catégorie dalgorithmes évolutionnistes, appelée algorithme bactériologique, pour améliorer automatiquement la qualité dun ensemble de cas de test pour un composant. Cet aspect de nos travaux a été publié dans ADDIN EN.CITE Baudry2000203Baudry, BenoitLe Traon, YvesJézéquel, Jean-MarcHanh, Vu Le2000Trustable Components: Yet Another Mutation-Based Approach1st Symposium on Mutation TestingSan Jose, CA69 - 76OctoberZ:\Articles&Présentations\Congrès\MUTATION2000\Baudry00c.pdfBaudry2000193Baudry, BenoitLe Traon, YvesHanh, Vu LeJézéquel, Jean-Marc2000Building Trust into OO Components using a Genetic AnalogyISSRE'00 (Int. Symposium on Software Reliability Engineering)San Jose, CA, USA4 - 14OctoberZ:\Articles&Présentations\Congrès\ISSRE2000\Baudry00b.pdfBaudry2000183Baudry, BenoitLe Traon, YvesHanh, Vu Le2000Testing-for-Trust: the Genetic Selection Model applied to Component QualificationTOOLS Europe (Technology of object-oriented languages and systems)Mont St Michel, FranceIEEE Computer Society108 - 119JuneZ:\Articles&Présentations\Congrès\TOOLS2000\Baudry00a.pdfBaudry2002853Baudry, BenoitFleurey, FranckLe Traon, YvesJézéquel, Jean-Marc2002Genes and Bacteria for Automatic Test Cases Optimization in the .NET EnvironmentISSRE'02 (Int. Symposium on Software Reliability Engineering)Annapolis, MD, USA195 - 206NovemberZ:\Articles&Présentations\Congrès\ISSRE2002\27_baudry_rev.pdfBaudry2002843Baudry, B.Fleurey, FranckLe Traon, YvesJézéquel, Jean-Marc2002Computational Intelligence for Testing .NET ComponentsMicrosoft Summer Research WorkshopCambrige, UKSeptemberZ:\Articles&Présentations\Congrès\SRW\Baudry-SRW-final.pdfBaudry2002833Baudry, B.Fleurey, FranckLe Traon, YvesJézéquel, Jean-Marc2002Automatic Test Cases Optimization using a Bacteriological Adaptation Model: Application to .NET ComponentsASE'02 (Automated Software Engineering)Edimburgh, Scotland, UK253 - 256SeptemberZ:\Articles&Présentations\Congrès\ASE2002\Baudry02d.pdf[Baudry''00a; Baudry''00b; Baudry''00c; Baudry''02b; Baudry''02c; Baudry''02d].
Dans la suite, nous étudions des problèmes de test qui peuvent apparaître lors de lassemblage de composants. Cette phase particulière du test est appelée test dintégration et consiste à tester les interactions entre les composants du système. A ce niveau, deux types derreur peuvent apparaître : des erreurs dans la définition des interfaces ou des incohérences dans le système dues à la décentralisation du contrôle.
Le premier type derreur peut entraîner une mauvaise utilisation dun composant par un autre. Pour éviter ces erreurs, la spécification des interfaces doit être la plus complète possible. Cette spécification pouvant être exprimée sous forme de contrats dans un cadre OO, la seconde contribution majeure présentée dans cette thèse consiste à étudier limpact des contrats sur deux facteurs de qualité dun logiciel : la robustesse (capacité dun logiciel à détecter une erreur interne) et la diagnosabilité (facilité pour la localisation dune erreur) ADDIN EN.CITE Baudry200133Baudry, BenoitLe Traon, YvesJézéquel, Jean-Marc2001Robustness and Diagnosability of Designed by Contracts OO SystemsMetrics'01 (Software Metrics Symposium)London, UK272 - 283AprilZ:\Articles&Présentations\Congrès\METRICS2001\140_letraon_jezequel_baudry.pdfLe Traon2003160Le Traon, YvesOuabdessalam, FaridRobach, ChantalBaudry, Benoit2003From Diagnosis to Diagnosability: Axiomatization, Measurement and ApplicationJournal of Systems and Software65131 - 50JanuaryD:\Travail\biblio-bb\diagnostic\jssLetraon.pdf[Baudry''01a; Le Traon''03].
Le second type derreur pouvant se produire lors de lintégration est dû au contrôle largement réparti à travers le système dans les logiciels orientés objet. En effet, lutilisation importante de certains mécanismes objets tels que le polymorphisme et la délégation, entraîne l'emploi dun grand nombre dobjets pour exécuter une fonctionnalité. Dans ce cas, des erreurs peuvent se produire lors de lutilisation de certains objets distants du système par dautres objets. La troisième point abordé au cours de cette thèse est donc létude dun critère de test pour assurer lintégration dun assemblage de composants, ainsi quune mesure de complexité associée à ce critère permettant de prévoir la testabilité dun assemblage. Les études de la testabilité de systèmes orientés objet ont été publiées dans ADDIN EN.CITE Baudry2001143Baudry, BenoitLe Traon, YvesSunyé, GersonJézéquel, Jean-Marc2001Towards a Safe Use of Design Patterns for OO Software TestabilityISSRE'01 (Int. Symposium on Software Reliability Engineering)Hong-Kong, China324 - 329NovemberZ:\Articles&Présentations\Congrès\ISSRE2001\29_baudry.pdfBaudry2002153Baudry, BenoitLe Traon, YvesSunyé, Gerson2002Testability Analysis of UML Class DiagramMetrics'02 (Software Metrics Symposium)Ottawa, Canada54 - 63JuneZ:\Articles&Présentations\Congrès\METRICS2002\baudry_testability.pdfBaudry20032053Baudry, B.Le Traon, YvesSunyé, GersonJézéquel, Jean-Marc2003Measuring and Improving Design Patterns TestabilityMetrics'03 (Software Metrics Symposium)Sydney, AustraliaSeptember[Baudry''01b; Baudry''02a; Baudry''03].
Ces trois contributions peuvent être classées suivant deux dimensions indépendantes et complémentaires : soit dans une perspective conception/validation, soit par un découpage composant/assemblage de composants.
Le premier aspect (conception/validation) consiste à répartir les travaux entre ceux qui traitent de la validation (côté « curatif ») et ceux qui traitent de la conception (côté « préventif »). Dans une répartition suivant cet aspect, les deux premières contributions peuvent être regroupées dans la catégorie validation, puisquelles concernent létude des composants autotestables (approche pragmatique préconisée dans léquipe Triskell). La troisième contribution, concerne létude de propriétés dun système qui peuvent être observés dés les premières phases de la conception (côté « préventif »).
Le second aspect (composant/assemblage) réparti les travaux entre ceux qui se concentrent sur un composant unitaire et ceux qui étudient le comportement dun assemblage de composants. Avec cette seconde répartition, la première contribution apparaît seule, puisquelle concerne la génération et loptimisation automatique de cas de test pour un composant isolé. Par ailleurs, les deux autres contributions concernant létude du comportement dun assemblage de composants vis-à-vis du test, sont alors regroupées suivant cet aspect.
Ainsi, il apparaît que, même si ces contributions sont clairement trois facettes dun même problème, elles peuvent être présentées suivant différents points de vue, rendant larticulation de ce document parfois difficile. Dans la suite, nous essayons dêtre le plus précis possible, et de resituer, si nécessaire, les différents points en fonction de ces deux aspects.
La suite de ce document est organisée de la manière suivante :
Le chapitre REF _Ref36869837 \r \h 2 rappelle les principes généraux pour le test de logiciel, puis présente létat de lart du test pour les programmes orientés objet. Au cours de ce chapitre, nous introduisons aussi une méthode pour la conception de composants logiciels fiables : les composants autotestables. Enfin, nous détaillons les travaux existant sur les points précis abordés dans cette thèse : la génération automatique de test, les assertions pour le test de logiciel et la testabilité.
Le chapitre REF _Ref36870530 \r \h 3 se concentre sur létude dun algorithme génétique pour résoudre le problème de loptimisation et la génération automatique de cas de test. Les résultats expérimentaux montrant une convergence trop lente, et discontinue, nous proposons un algorithme original, mieux adapté à la génération dun ensemble de cas de test. Nous appelons cet algorithme un algorithme bactériologique, et validons son efficacité expérimentalement.
Le chapitre REF _Ref36871059 \r \h 4 concerne létude dun autre aspect de la méthode : la conception par contrat (design by contract). Nous étudions ici limpact de cette technique pour la détection et la localisation derreur. Les mesures de robustesse et de diagnosabilité sont définies pour évaluer cet impact, et les facteurs influençant ces mesures sont calibrés par plusieurs expériences. Les résultats valident à la fois lapproche par contrat et lintérêt des composants autotestables pour augmenter la confiance dans le composant.
Le chapitre REF _Ref36871466 \r \h 5 présente le dernier point étudié dans cette thèse : la testabilité dun assemblage de composants. Nous proposons un critère de test pour la couverture dinteractions entre objets, ainsi quune mesure de complexité associée. Ce critère peut être évalué sur un diagramme de classes, et permet ainsi davoir une mesure prédictive sur la qualité de limplantation. Nous présentons aussi une méthode pour lamélioration de la testabilité dun programme à partir du diagramme de classes. Enfin, au cours de cette étude nous nous concentrons sur les design patterns comme un sous-ensemble cohérent dans un diagramme de classes pour lanalyse de testabilité à un niveau local. Nous proposons une analyse détaillée de la testabilité de lapplication de designs patterns ; quels problèmes classiques de testabilité peuvent être résolus, et quels sont les nouveaux points complexes pour le test.
Enfin, le chapitre REF _Ref36872338 \r \h 6 conclut cette thèse et trace un panorama des principales pistes de recherche et perspectives qui se dégagent à partir de ces travaux.
Etat de lart
La programmation orientée objets a introduit de nouvelles structures qui doivent être prises en compte pour le test de logiciels. Lencapsulation des données dans des classes, lhéritage, le polymorphisme, font lessentiel 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 dun système à objets comme une succession détapes dinté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é ADDIN EN.CITE Siegel19961451Siegel, Shel1996Object Oriented Software TestingMarjorie SpencerJohn Wiley & Sons511John Wiley & Sons0-471-13749-9Binder199941Binder, R. V.1999Testing Object-Oriented Systems: Models, Patterns and ToolsAddison-WesleyISBN 0-201-80938-9McGregor20011461McGregor, John D.2001A Practical Guide To Testing Object Oriented TestingObject Technology SeriesAddison Wesley0-201-32564-0[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 napportant 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 lapparition 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 lutilisation dassertions/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 lanalyse 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 dintroduire le domaine du test, dabord dun 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 lactivité 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 dune technique de validation de cas de test très largement utilisée au cours des travaux décrits dans cette thèse : lanalyse de mutation. Nous présentons ensuite quelques éléments de la notation UML utilisés pour le test, et continuons avec lintroduction dune méthode pour la conception de composants logiciels fiables. Enfin, les trois dernières sections présentent des états de lart 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, limpact de la conception par contrat pour le test et la testabilité des logiciels.
Le test de logiciel
Dans cette première section, nous présentons lapproche 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 lanalyse 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 REF _Ref38366758 \r \h 3 et REF _Ref38366759 \r \h 4.
Le 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 ADDIN EN.CITE Laprie19951951Laprie, Jean-Claude1995Guide de la surete de fonctionnementCepadues2-85428-341-4[Laprie''95]. Une faute désigne la cause dune 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 laccomplissement de la fonction du système. Dans la suite de ce chapitre nous nutiliserons 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 lerreur est corrigée, il convient de retester le programme pour sassurer de la correction et la non régression du programme. Enfin, pour être complète, lactivité de test implique de déterminer un « critère darrê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 sinspirant de ADDIN EN.CITE Xanthakis19921753Xanthakis, S.Ellis, C.Skourlas, C.Le Gall, A.Katsikas, S.Karapoulios, K.1992Genetic Algorithms Applications to Software TestingFifth International Conference. Software Engineering and Its ApplicationsToulouse, France625 - 636December[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 quun cas de test désigne le programme chargé dexé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é à lexécution avec les données de test en entrée. Par exemple, pour tester le retour dun livre dans une bibliothèque, il faut dabord emprunter le livre. Au cours du chapitre REF _Ref32896132 \r \h 3, nous nous intéressons au problème de la génération automatique de données de test efficaces. Lefficacité de ces données est évaluée grâce à lanalyse de mutation qui est présentée dans la suite de ce chapitre.
Après lexé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 doracle. Ce prédicat peut être explicite dans les cas de test, être obtenu indirectement par des assertions ou dautres moyens (par exemple, le model-checking) ou, dans le cas le pire, être implicite et dépendre dun verdict humain. Il est clair que loracle 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 loracle dêtre correct, cest-à-dire de ne pas marquer comme erroné un comportement correct. Loracle est donc le reflet, plus ou moins complet, et exécutable, de la spécification.
Lorsquun 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 à loracle, 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 darrê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 darrêt particulier (plutôt quà chercher à détecter des erreurs). Pour générer des cas de test en fonction dun critère de test, il est possible de définir des objectifs de test. Par exemple, si le critère exige lexécution de toutes les instructions du programme au cours du test, « couvrir linstruction 11 » est un objectif de test possible. Il faut ensuite écrire un cas de test qui vérifie cet objectif. Cette notion dobjectif de test sapplique non seulement aux aspects structurels (couverture de code), mais aussi aux aspects comportementaux (observation dun certain échange de messages ADDIN EN.CITE Pickin20021683Pickin, SimonJard, ClaudeLe Traon, YvesJéron, T.Jézéquel, Jean-MarcLe Guennec, Alain2002System Test Synthesis from UML Models of Distributed SoftwareFORTEZ:\biblio-bb\TestOO\UMLbased\UML-TGV.pdf[Pickin''02]). Le chapitre REF _Ref41904964 \r \h 5 définit un critère de test pour les logiciels OO, et sen sert comme support pour une analyse prédictive de la testabilité visant à estimer leffort de test.
Les techniques pour la génération de cas de test sont de deux types : le test fonctionnel et le test structurel ADDIN EN.CITE Beizer19901791Beizer, Boris1990Software Testing TechniquesVan Norstrand Reinhold0-442-20672-0[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 dune 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 limplantation change ; elle ne garantit cependant pas que tout le programme ait été couvert par les cas de test.
Le test structurel sappuie 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 ADDIN EN.CITE Beizer19901791Beizer, Boris1990Software Testing TechniquesVan Norstrand Reinhold0-442-20672-0[Beizer''90], graphe flot de données ADDIN EN.CITE Rapps1985520Rapps, S. Weyuker, E. J.1985Selecting Software Test Data Using Data Flow InformationIEEE Transactions on Software Enginnering114367 - 375April[Rapps''85]). Cette représentation permet de définir des critères de couverture indépendants dun programme particulier : couverture de tous les arcs du graphe, tous les nuds, 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 à dautres aspects tels que les performances, le test en charge
le test de logiciels orientés objet
Les mécanismes propres aux langages orientés objet et les méthodes danalyse 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 quelle sinté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 dun oracle, et lécriture des drivers de test. En ce qui concerne les critères de couverture, ADDIN EN.CITE Harrold199473Harrold, M. J.Rothermel, G.1994Performing Data Flow Testing on ClassesFSE (Foundation on Software Engineering)New Orleans, US154 - 163DecemberBuy20001493Buy, UgoOrso, AlessandroPezzè, Mauro2000Automated Testing of ClassesISSTA'00Portland, OR, USA39 - 48AugustZ:\biblio-bb\TestOO\AutomatedTestingClass.pdf[Harrold''94; Buy''00] étudient les critères flot de données et flot de contrôle pour le test dune classe. Dans ADDIN EN.CITE Harrold199473Harrold, M. J.Rothermel, G.1994Performing Data Flow Testing on ClassesFSE (Foundation on Software Engineering)New Orleans, US154 - 163December[Harrold''94], Harrold et al. proposent trois niveaux de granularité (intra-méthode, inter-méthode et intra-classe) pour lanalyse des flots de données dans une classe. Ils donnent lalgorithme pour construire le graphe et lillustrent sur une classe. Buy et al. ADDIN EN.CITE Buy20001493Buy, UgoOrso, AlessandroPezzè, Mauro2000Automated Testing of ClassesISSTA'00Portland, OR, USA39 - 48AugustZ:\biblio-bb\TestOO\AutomatedTestingClass.pdf[Buy''00] repartent des travaux de Harrold et étendent lapproche à des techniques dexécution symbolique et de déduction automatique, dans le but de générer automatiquement des séquences dappels 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 lapproche sur plusieurs études de cas, et veulent maintenant développer les outils pour lautomatiser. Quant à McGregor ADDIN EN.CITE McGregor20011461McGregor, John D.2001A Practical Guide To Testing Object Oriented TestingObject Technology SeriesAddison Wesley0-201-32564-0[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 ADDIN EN.CITE Buy20001493Buy, UgoOrso, AlessandroPezzè, Mauro2000Automated Testing of ClassesISSTA'00Portland, OR, USA39 - 48AugustZ:\biblio-bb\TestOO\AutomatedTestingClass.pdf[Buy''00], les auteurs sintéressent à lexécution symbolique, McGregor ADDIN EN.CITE McGregor20011461McGregor, John D.2001A Practical Guide To Testing Object Oriented TestingObject Technology SeriesAddison Wesley0-201-32564-0[McGregor''01], propose dutiliser 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 ADDIN EN.CITE Baudry2000193Baudry, BenoitLe Traon, YvesHanh, Vu LeJézéquel, Jean-Marc2000Building Trust into OO Components using a Genetic AnalogyISSRE'00 (Int. Symposium on Software Reliability Engineering)San Jose, CA, USA4 - 14OctoberZ:\Articles&Présentations\Congrès\ISSRE2000\Baudry00b.pdfBaudry2002853Baudry, BenoitFleurey, FranckLe Traon, YvesJézéquel, Jean-Marc2002Genes and Bacteria for Automatic Test Cases Optimization in the .NET EnvironmentISSRE'02 (Int. Symposium on Software Reliability Engineering)Annapolis, MD, USA195 - 206NovemberZ:\Articles&Présentations\Congrès\ISSRE2002\27_baudry_rev.pdf[Baudry''00b; Baudry''02d]). Les travaux qui se sont intéressés à la production de loracle pour le test unitaire de classes, sont tous fondés sur la spécification de post-conditions pour les méthodes de la classe ADDIN EN.CITE Edwards20011480Edwards, S. H.2001A Framework for Practical, Automated Balck-Box Testing of Component-Based SoftwareSoftware Testing, Verification and Reliability11297 - 111JuneZ:\biblio-bb\TestGeneration\AutomatedTestingForCompBasedSoft.pdfCheon20021043Cheon, YoonsikLeavens, Gary T.2002A Simple and Practical Approach to Unit Testing: The JML and JUnit WayECOOPMalaga, Spain2374231 - 255Springer-VerlagLecture Notes in Computer ScienceJuneZ:\biblio-bb\TestOO\UnitTestingJML&JUnit.pdfFenkam20021153Fenkam, PascalGall, HaraldJazayeri, Mehdi2002Constructing Corba-Supported Oracles for Testing: A Case Study in Automated Software TestingASE'02 (Automated Software Engineering)Edimburgh, UK129 - 138September[Edwards''01; Cheon''02; Fenkam''02]. Ces travaux sont détaillés dans la section REF _Ref38794451 \r \h 2.5 sur lutilisation des contrats pour le test.
Enfin, pour lorganisation des cas de tests, et la production de drivers de test, ADDIN EN.CITE Jézéquel200180Jézéquel, Jean-MarcDeveaux, D.Le Traon, Yves2001Reliable Objects: a Lightweight Approach Applied to JavaIEEE Software18476 - 83July/August[Jézéquel''01] propose dembarquer les cas de test dans la classe sous test. Cest lidée de classe auto-testable sur laquelle sappuie en partie cette thèse, et qui sera détaillée dans la section suivante. Dautre part, la méthode dite deXtremeProgramming proposée par Kent Beck ADDIN EN.CITE Beck1999641Beck, Kent1999Extreme programming explainedAddison-Wesley1900-201-61641-6[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 ADDIN EN.CITE Craig20026316Craig, Philip2002NUnit2002Mayhttp://nunit.sourceforge.net/Beck20014816Beck, KentGamma, E.2001JUnit2002http://www.junit.org/index.htm[Beck''01; Craig''02]
Une bonne structuration des cas de test unitaires offre aussi lavantage dune 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 dintégration. En effet, les systèmes OO peuvent être vus comme lassemblage 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 dintégration consiste alors à vérifier que les unités clientes utilisent les unités serveur en conformité avec linterface offerte par lunité 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 ADDIN EN.CITE Wu20031623Wu, YeChen, Mei-HwaOffutt, A. J.2003UML-based Integration testing for Component-based SoftwareInternational Conference on COTS-based Software SystemsOttawa, CanadaFebruaryZ:\biblio-bb\TestOO\UMLBasedIntegration.pdfMartena20021503Martena, V.Orso, AlessandroPezzè, Mauro2002Interclass Testing of Object Oriented SoftwareInternational Conference on Engineering of Complex Computer SystemsZ:\biblio-bb\TestOO\InterClassTesting.pdfAlexander200023Alexander, R. T.Offutt, Jeff2000Criteria for Testing Polymorphic RelationshipsISSRE'00 (Int. Symposium on Software Reliability Engineering)San Jose, CA, USA15 - 23OctoberD:\Travail\biblio-bb\TestOO\CriteriaForPolymorph.pdfAlexander20021243Alexander, R. T.Offutt, A. J.Bieman, J.M.2002Fault Detection Capabilities of Coupling-Based OO TestingISSRE'02 (Int. Symposium on Software Reliability Engineering)Annapolis, MD, USA207 - 218NovemberChen19991573Chen, Mei-HwaKao, H.M.1999Testing object-oriented programs - an integrated approachISSRE'99 (Int. Symposium on Software Reliability Engineering)Boca Raton, FL, USA73 - 82NovemberZ:\biblio-bb\TestOO\TestCriteriaForOO.pdfBaudry2002153Baudry, BenoitLe Traon, YvesSunyé, Gerson2002Testability Analysis of UML Class DiagramMetrics'02 (Software Metrics Symposium)Ottawa, Canada54 - 63JuneZ:\Articles&Présentations\Congrès\METRICS2002\baudry_testability.pdf[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 dune unité serveur par une unité cliente. De plus, dans ADDIN EN.CITE Wu20031623Wu, YeChen, Mei-HwaOffutt, A. J.2003UML-based Integration testing for Component-based SoftwareInternational Conference on COTS-based Software SystemsOttawa, CanadaFebruaryZ:\biblio-bb\TestOO\UMLBasedIntegration.pdf[Wu''03] les auteurs décrivent les éléments de spécification UML nécessaires au test dintégration de composants. Wu et al. décrivent les descriptions comportementales minimum pour pouvoir tester les interactions avec un composant dont le code source nest pas disponible. Les auteurs de ADDIN EN.CITE Ghosh20001553Ghosh, S.Mathur, A.2000Interface Mutation to assess the adequacy of tests for components and systemsTOOLSSanta Barbara, CA, USA37 - 46AugustZ:\biblio-bb\Mutation\InterfaceMutation.pdfHoijin19981563Hoijin, YoonByoungju, ChoiJin-Ok, Jeon1998Mutation-based inter-class testingAsia Pacific Software Engineering ConferenceTaipei, Taiwan174 - 181DecemberZ:\biblio-bb\Mutation\MutationBasedInterClassTest.pdf[Hoijin''98; Ghosh''00] proposent dutiliser des techniques de mutation sur les interfaces des composants à intégrer pour valider le test dintégration.
Un problème complémentaire au test dintégration, est dobtenir un ordre dintégration des composants dun 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 dune classe pour pouvoir en tester une autre. Or, lécriture dun simulateur peut être très coûteuse, et plusieurs travaux ont étudié des techniques pour trouver un ordre dintégration qui minimise la génération de simulateurs ADDIN EN.CITE Kung199690Kung, D. C.Gao, J.Hsia, P.Toyashima, Y.Chen, C.1996On Regression Testing of Object-Oriented ProgramsThe Journal of Systems and Software32121 - 40JanuaryLabiche20001633Labiche, YvanThevenod-Fosse, P.Waeselynck, H.Durand, M.-H.2000Testing levels for object-oriented softwareICSELimerick, IrelandJuneZ:\biblio-bb\TestOO\OrdreDeTest.pdfBriand2001533Briand, LionelLabiche, Yvan2001Revisiting Strategies for Ordering Class Integration Testing in the Presence of Dependency CyclesISSRE'01 (Int. Symposium on Software Reliability Engineering)Hong-Kong, China287 - 296DecemberTai19991860Tai, K. C.Daniels, F. J.1999Inter-Class Test Order for Object-Oriented SoftwareJournal of Object Oriented Programming12418 - 35July-AugustHanh20021852Hanh, Vu Le2002Test et modèle UML : stratégie, plan et synthèse de testIRISARennesUniversité de Rennes 1[Kung''96; Tai''99; Labiche''00; Briand''01; Hanh''02]. Soundarajan et al. présentent une étude en rapport avec lordre dintégration dans ADDIN EN.CITE Soundarajan20011783Soundarajan, N.Tyler, B.2001Specification-based incremental testing of object oriented systemsTOOLS 39Santa Barbara, CA, USA35 - 44JulyZ:\biblio-bb\TestOO\SpecBasedOOTesting.pdf[Soundarajan''01]. Cet article est fondé sur le fait que lhé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 ADDIN EN.CITE Briand2002540Briand, LionelLabiche, Yvan2002A UML-based approach to System TestingJournal of Software and Systems Modeling1110 - 42SeptemberNebut20021673Nebut, ClémentinePickin, SimonLe Traon, YvesJézéquel, Jean-Marc2002Reusable Test Requirements for UML-Modeled Product LinesREPLEssen, Germany51 - 56SeptemberZ:\biblio-bb\TestOO\UMLbased\TestPattern.pdfPickin20021683Pickin, SimonJard, ClaudeLe Traon, YvesJéron, T.Jézéquel, Jean-MarcLe Guennec, Alain2002System Test Synthesis from UML Models of Distributed SoftwareFORTEZ:\biblio-bb\TestOO\UMLbased\UML-TGV.pdf[Briand''02a; Nebut''02; Pickin''02]. Ces trois méthodes sont fondées sur la conjonction dinformations obtenues à partir de plusieurs vues UML du système. Les auteurs de ADDIN EN.CITE Briand2002540Briand, LionelLabiche, Yvan2002A UML-based approach to System TestingJournal of Software and Systems Modeling1110 - 42September[Briand''02a] proposent dutiliser les cas dutilisation et les diagrammes de collaboration associés pour ordonner les cas de test dans une suite de tests système, puis dutiliser les diagrammes de séquence ainsi que les pré et post conditions sur les méthodes pour dériver des cas de test. Dans ADDIN EN.CITE Nebut20021673Nebut, ClémentinePickin, SimonLe Traon, YvesJézéquel, Jean-Marc2002Reusable Test Requirements for UML-Modeled Product LinesREPLEssen, Germany51 - 56SeptemberZ:\biblio-bb\TestOO\UMLbased\TestPattern.pdf[Nebut''02], lidé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 dun diagramme de classe particulier. Enfin ADDIN EN.CITE Pickin20021683Pickin, SimonJard, ClaudeLe Traon, YvesJéron, T.Jézéquel, Jean-MarcLe Guennec, Alain2002System Test Synthesis from UML Models of Distributed SoftwareFORTEZ:\biblio-bb\TestOO\UMLbased\UML-TGV.pdf[Pickin''02] sappuie sur la machine à états associée à chaque classe du système, et une description sous forme de diagramme de séquence dun objectif de test, pour générer un cas de test correspondant à lobjectif.
Enfin, certains travaux se concentrent sur certaines vues dUML 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 ADDIN EN.CITE Antoniol20021273Antoniol, G.Briand, LionelDiPenta, M.Labiche, Yvan2002A case Study Using The Round-Trip Strategy for State-Based Class TestingISSRE'02 (Int. Symposium on Software Reliability Engineering)Annapolis, MD, USA269 - 279NovemberBogdanov20011350Bogdanov, K.Holcombe, M.2001Statechart Testing Method for Aircraft Control SystemsSoftware Testing, Verification and Reliability11139 - 54MarchZ:\biblio-bb\TestOO\StateBasedGeneration\aircraftControl.pdfChevalley20011343Chevalley, PhillipeThévenod-Fosse, Pascale2001Automated generation of statistical test cases from UML state diagramsCOMPSAC (25th Annual International Computer Software and Applications Conference)Chicago, IL, USA205 - 214OctoberZ:\biblio-bb\TestOO\UMLbased\StateBasedGeneration\statisticalTestFromUMLStatechart.pdfKim19991330Kim, Y.G. Hong, H.S.Bae, D.H.Cha, S.D.1999Test cases generation from UML state diagramsIEEE Proceedings-Software1464187 - 192AugustZ:\biblio-bb\TestOO\geneFromUMLStateDiag.pdfOffutt19991313Offutt, A. J.Abdurazik, A.1999Generating Tests from UML SpecificationsUML'99Fort Collins, CO, USA416 - 429OctoberZ:\biblio-bb\testFromUMLSpec.pdfChevalley20011693Chevalley, PhillipeThevenod-Fosse, P.2001An Empirical Evaluation of Statistical Testing Designed from UML State Diagrams: the Flight Guidance System Case StudyISSRE'01 (Int. Symposium on Software Reliability Engineering)Hong Kong, China27 - 30November[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 dun tel diagramme. Les auteurs de ADDIN EN.CITE Abdurazik2000503Abdurazik, A.Offutt, A. J.2000Using UML Collaboration Diagrams for Static Checking and Test GenerationSpringer-VerlagUML'00York, UK383 - 395October[Abdurazik''00] utilisent le diagramme de collaborations pour tester certaines propriétés des routines décrites à laide de ces diagrammes.
Dans la section suivante nous nous concentrons sur une technique pour évaluer la qualité des cas de test pour un composant : lanalyse de mutation. Cette méthode qui fut dabord proposée pour les programmes procéduraux, a ensuite été adaptée pour le test de logiciels orientés objet.
Lanalyse de mutation
Lanalyse de mutation est une technique pour la validation de données de test fondée sur linjection de fautes. Linsertion systématique derreurs dans un logiciel est souvent utilisée pour la validation expérimentale de techniques de test. La connaissance du nombre et de lemplacement précis des erreurs dans le logiciel permet en effet de contrôler lefficacité de techniques ou méthodes de test ou de diagnostic ADDIN EN.CITE Jones20021233Jones, James A.Harrold, Mary JeanStasko, John2002Visualization of Test Information to Assist Fault LocalizationICSE'02Orlando, FL, USA467 - 477MayD:\Travail\biblio-bb\diagnostic\icse02(harrold).pdf[Jones''02].
Lanalyse de mutation est une technique proposée par DeMillo ADDIN EN.CITE DeMillo1978270DeMillo, R.Lipton, R.Sayward, F.1978Hints on Test Data Selection : Help For The Practicing ProgrammerIEEE Computer11434 - 41AprilZ:\biblio-bb\Mutation\HintsTestDataSelectino.pdf[DeMillo''78] et peut être utilisée de deux façons : évaluer la qualité dun 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, lensemble des mutants est créé en soumettant le programme à des opérateurs de mutation qui correspondent à différents types derreur (changement de signe des constantes, changement de sens dune inégalité
).
Le résultat de lexécution des cas de test avec les mutants permet dune part dévaluer lefficacité de lensemble de cas de test par la proportion de mutants détectée (appelée le score de mutation). Dautre part, lanalyse des erreurs qui nont 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 quil tue le mutant, sinon le mutant est vivant.
Chaque mutant ne comporte quune seule erreur simple. Cette limitation est fondée sur deux hypothèses : lhypothèse du programmeur compétent, et leffet de couplage. La première hypothèse met en avant le fait quun 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). Lhypothèse sur leffet 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 ADDIN EN.CITE Offutt1992390Offutt, A.J.1992Investigations of the software testing coupling effectACM Transactions on Software Engineering and Methodology115 - 20JanuaryZ:\biblio-bb\Mutation\CouplingEffect.pdf[Offutt''92]:
Faut simple, faute complexe. Une faute simple, est une faute qui peut être corrigée en ne modifiant quune seule instruction dans le code source. Une faute complexe est une faute qui ne peut pas être corrigée en ne modifiant quune seule instruction.
Dans ce même article, Offutt étudie linjection de plusieurs erreurs. Il exécute ensuite les cas de test efficaces avec mutants ne comportant quune 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 lhypothèse sur leffet de couplage.
Offutt étudie aussi lefficacité de lanalyse de mutation en la comparant à des critères flot de donnée dans ADDIN EN.CITE Offutt1996250Offutt, A. J.Pan, J.Tewary, K. Zhang, T.1996An experimental evaluation of data flow and mutation testingSoftware Practice and Experience262February[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 lautre critère, et il compare le pouvoir de détection derreur de chacun. Il conclut que leffort 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 derreurs.
Les erreurs injectées pour lanalyse de mutation correspondent à différents types derreurs 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 ADDIN EN.CITE Offutt19961030Offutt, A. J.Lee, AmmeiRothermel, G.Untch, Roland H.Zapf, Christian1996An Experimental Determination of Sufficient Mutant OperatorsACM Transactions on Software Engineering and Methodology5299 - 118AprilZ:\biblio-bb\Mutation\SuficientMutationOperators.pdf[Offutt''96b].
La génération de cas de test fondée sur lanalyse de mutation est fastidieuse, puisque tant quune proportion importante de mutants nest 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 dentre eux. Dans ADDIN EN.CITE DeMillo1991380DeMillo, R.Offutt, A.J.1991Constraint-Based Automatic Test Data GenerationIEEE Transactions on Software Enginnering179900 - 910SeptemberZ:\biblio-bb\TestGeneration\ConstraintBased.pdf[DeMillo''91], les auteurs donnent une technique pour la génération automatique de cas de test fondée sur lanalyse de mutation. Lidée quils développent dans cet article, et quils ont affinée par la suite, est de fixer comme objectif de test lendroit du programme où se trouve lerreur, et de remonter toutes les contraintes sur les données entre cet endroit et lentrée du programme. Ils peuvent ainsi générer une donnée de test qui atteint lerreur et permet de détecter le mutant. Un outil pour le langage Fortran a été développé ADDIN EN.CITE DeMillo19931640DeMillo, R.Offutt, A.J.1993Experimental results from an automatic test case generatorACM Transactions on Software Engineering and Methodology22109 - 27AprilZ:\biblio-bb\TestGeneration\GenerationwMutation.pdf[DeMillo''93].
Lanalyse de mutation a dabord é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 ADDIN EN.CITE Ghosh20001553Ghosh, S.Mathur, A.2000Interface Mutation to assess the adequacy of tests for components and systemsTOOLSSanta Barbara, CA, USA37 - 46AugustZ:\biblio-bb\Mutation\InterfaceMutation.pdfKim2001590Kim, Sun-Woo Clark, J.A. McDermid, J.A.2001Investigating the effectiveness of object-oriented testing strategies using the mutation methodSoftware Testing, Verification and Reliability114207 - 225DecemberZ:\biblio-bb\Mutation\EffectiveOOMutation.pdfAlexander20021263Alexander, R. T.Bieman, J.M.Sudipto, GhoshBixia, Ji2002Mutation of Java ObjectsISSRE'02 (Int. Symposium on Software Reliability Engineering)Annapolis, MD, USA341 - 351NovemberMa20021253Ma, Yu-SeungKwon, Yong-RaeOffutt, A. J.2002Inter-Class Mutation OperatorsISSRE'02 (Int. Symposium on Software Reliability Engineering)Annapolis, MD, USA352 - 363NovemberZ:\biblio-bb\Mutation\InterClassMutation.pdfChevalley20011703Chevalley, Phillipe2001Applying Mutation Analysis for Object-Oriented Programs Using a Reflective ApproachEighth Asia-Pacific Software Engineering ConferenceMacao, China267 - 70DecemberZ:\biblio-bb\Mutation\MutationForOOwReflection.pdfHoijin19981563Hoijin, YoonByoungju, ChoiJin-Ok, Jeon1998Mutation-based inter-class testingAsia Pacific Software Engineering ConferenceTaipei, Taiwan174 - 181DecemberZ:\biblio-bb\Mutation\MutationBasedInterClassTest.pdf[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 ADDIN EN.CITE Kim2001590Kim, Sun-Woo Clark, J.A. McDermid, J.A.2001Investigating the effectiveness of object-oriented testing strategies using the mutation methodSoftware Testing, Verification and Reliability114207 - 225DecemberZ:\biblio-bb\Mutation\EffectiveOOMutation.pdfAlexander20021263Alexander, R. T.Bieman, J.M.Sudipto, GhoshBixia, Ji2002Mutation of Java ObjectsISSRE'02 (Int. Symposium on Software Reliability Engineering)Annapolis, MD, USA341 - 351NovemberChevalley20011703Chevalley, Phillipe2001Applying Mutation Analysis for Object-Oriented Programs Using a Reflective ApproachEighth Asia-Pacific Software Engineering ConferenceMacao, China267 - 70DecemberZ:\biblio-bb\Mutation\MutationForOOwReflection.pdfMa20021253Ma, Yu-SeungKwon, Yong-RaeOffutt, A. J.2002Inter-Class Mutation OperatorsISSRE'02 (Int. Symposium on Software Reliability Engineering)Annapolis, MD, USA352 - 363NovemberZ:\biblio-bb\Mutation\InterClassMutation.pdf[Chevalley''01c; Kim''01; Alexander''02b; Ma''02], alors que dautres utilisent cette technique pour valider des cas de test dintégration ADDIN EN.CITE Ghosh20001553Ghosh, S.Mathur, A.2000Interface Mutation to assess the adequacy of tests for components and systemsTOOLSSanta Barbara, CA, USA37 - 46AugustZ:\biblio-bb\Mutation\InterfaceMutation.pdfHoijin19981563Hoijin, YoonByoungju, ChoiJin-Ok, Jeon1998Mutation-based inter-class testingAsia Pacific Software Engineering ConferenceTaipei, Taiwan174 - 181DecemberZ:\biblio-bb\Mutation\MutationBasedInterClassTest.pdf[Hoijin''98; Ghosh''00] ou unitaires ADDIN EN.CITE Baudry2000193Baudry, BenoitLe Traon, YvesHanh, Vu LeJézéquel, Jean-Marc2000Building Trust into OO Components using a Genetic AnalogyISSRE'00 (Int. Symposium on Software Reliability Engineering)San Jose, CA, USA4 - 14OctoberZ:\Articles&Présentations\Congrès\ISSRE2000\Baudry00b.pdf[Baudry''00b]. Enfin, dans ADDIN EN.CITE Chevalley20011703Chevalley, Phillipe2001Applying Mutation Analysis for Object-Oriented Programs Using a Reflective ApproachEighth Asia-Pacific Software Engineering ConferenceMacao, China267 - 70DecemberZ:\biblio-bb\Mutation\MutationForOOwReflection.pdf[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 dintrospection du langage.
Conclusion
Le test est une technique importante pour la validation de logiciels, et il existe de nombreux travaux qui sinté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 lanalyse 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 dabord 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 REF _Ref38794451 \r \h 2.5 détaille la méthode de conception par contrat, ainsi quune méthode plus générale pour la conception de composants logiciels fiables.
Éléments de la notation UML pour le test de logiciels OO
Nous définissons ici les principaux éléments de la notation UML ADDIN EN.CITE Fowler1997921Fowler, MartinScott, KendalBooch, Grady1997UML distilledObject Oriented seriesAddison-Wesley1790-201-32563-2Booch19991661Booch, GradyRumbaugh, JamesJacobson, Ivar1999The Unified Modeling Language User GuideAddison-Wesley object technology series4822-201-57168-6[Fowler''97; Booch''99] qui sont utilisés pour le test de logiciels OO. UML propose neuf diagrammes différents, aussi appelés vues. Nous présentons les diagrammes de classes, de séquence, dactivité et détat. Il existe plusieurs travaux sur le test OO fondés sur ces vues, qui seront présentées dans la suite de cette section. Le diagramme de classes est aussi largement utilisé comme base pour les travaux présentés dans cette thèse, ainsi que les diagrammes de séquence et détats pour illustrer certaines idées. Il existe six autres vues (cas dutilisation, diagramme dactivité et de collaboration, diagramme dobjets, diagramme de déploiement, et diagramme de paquetage) qui ne sont pas détaillées ici, car elles sont peu exploitées dans le cadre de cette thèse.
Diagramme de classes
Le diagramme de classes est la vue principale pour le langage UML. Il permet de décrire statiquement les classes présentes dans un système et les relations entre ces classes.
Tout dabord, une classe contient plusieurs informations : un nom, des attributs et des méthodes. La REF _Ref29532556 \h Figure 1 montre la notation UML pour une classe Livre qui a un attribut titre et trois méthodes getTitre, emprunter et retour. Il existe trois types de visibilité pour les classes, les attributs et les méthodes : privé, protégé, public. La visibilité privée restreint laccès à cet élément aux éléments de la même classe, un élément protégé est accessible aux descendants de la classe et aux éléments dun même paquetage, et un élément public est accessible à tous les autres éléments du système. La notation UML pour la visibilité est la suivante : - pour privé, # pour partagé et + pour public. Enfin, une classe peut être abstraite ou une simple interface. Une classe abstraite peut contenir des attributs et des méthodes, mais le corps de certaines méthodes est vide et elles doivent être définies dans des classes filles. Le nom dune classe abstraite est noté en italique dans un diagramme de classes. Une interface correspond au cas extrême dune classe abstraite puisque aucun attribut ni aucune méthode nont dimplantation. Une classe dinterface sert à spécifier les signatures des méthodes qui seront implantées par toutes ses sous-classes. Le nom dune interface est précédé par « interface » dans un diagramme de classes. La notation UML pour ces deux types de classe particuliers est donnée REF _Ref29534986 \h Figure 2.
Figure SEQ Figure \* ARABIC 1 Une classe Livre
Figure SEQ Figure \* ARABIC 2 Classe abstraite et interface
Ensuite il existe des relations entre les classes dun système. UML permet de décrire trois types de relations : une relation permanente appelée une association, une relation temporaire appelée une dépendance, et la relation dhéritage ou spécialisation. Une association peut être renforcée soit par une agrégation, soit par une composition. La REF _Ref29532574 \h Figure 3 illustre ces cinq types de relation de la classe classB vis-à-vis de classA : dépendance, héritage, association, agrégation, composition. Une association (et une agrégation/composition) peut comporter cinq informations supplémentaires : le rôle de cette association, les rôles de chaque classe pour cette association et les cardinalités de chaque classe pour cette association. La REF _Ref29533185 \h Figure 4 illustre la notation pour ces informations : lassociation entre les classes Livre et EvtLivre joue le rôle des gestionEvts, une instance de la classe Livre encapsule un ensemble (cardinalité *) dinstances de la classe EvtLivre, et cet ensemble joue le rôle de commandes vis-à-vis du livre, enfin une instance de la classe EvtLivre est référée par une seule instance (cardinalité 1) de Livre qui joue le rôle de sujet.
Le notion dhéritage, présente dans les langages à objets, est un mécanisme important pour lextension et la réutilisation : quand une classe fille hérite dune classe mère, elle hérite de tous les comportements de la classe mère, et peut en définir de nouveaux qui lui sont propres. Par ailleurs, lhéritage est un mécanisme dabstraction. Par exemple, si une classe cliente c utilise les services fournis par une classe fournisseur f qui se spécialise en plusieurs classes (f1,
, fn), le client ne sait pas, a priori, quelle classe fi va effectivement exécuter le service requis. Le mécanisme qui permet de créer, à lexécution, une association effective entre deux classes, sappelle la liaison dynamique.
Figure SEQ Figure \* ARABIC 3 - Cinq relations possibles entre classes
Figure SEQ Figure \* ARABIC 4 - Les rôles et cardinalités sur une association
Vues dynamiques : diagrammes de séquence et détats
Les diagrammes de séquence permettent de représenter des collaborations entre objets selon un point de vue temporel, on y met l'accent sur la chronologie des envois de messages. Sur ce diagramme, chaque objet est représenté par un axe vertical. L'ordre d'envoi d'un message est déterminé par sa position sur l'axe vertical du diagramme ; le temps s'écoule "de haut en bas" sur cet axe.
Un exemple de diagramme de séquence est illustré par la REF _Ref32650068 \h Figure 5. Les axes verticaux représentent soit une instance dun acteur, soit une instance dune classe. Le diagramme de séquence décrit les échanges de messages entre ces instances, la création dynamique dobjets, et une alternative pour léchange de messages. Plusieurs constructions graphiques existent pour spécifier lalternative ou la récursivité dans un diagramme de séquence
Le diagramme détats permet de représenter un automate d'états finis, sous forme de graphe d'états reliés par des arcs orientés qui décrivent les transitions. Les diagrammes d'états permettent de décrire les changements d'états d'un objet ou d'un composant, en réponse aux interactions avec d'autres objets/composants ou avec des acteurs. Un état peut être simple, ou composé lui-même de sous-états et de transitions entre ces états (on parle alors de super-état). On distingue un état initial, et il peut y avoir plusieurs états terminaux.
Une transition est atomique et représente donc le passage supposé instantané d'un état vers un autre. Trois éléments peuvent étiqueter une transition : une garde, un évènement et une action. La transition est déclenchée par lévènement, la garde est une expression booléenne qui conditionne le déclenchement de la transition si elle existe. Enfin, il est possible dassocier une action à lévènement, ce qui, en pratique, revient à dire que lon fait un traitement dans le système (appel de méthode, changement de valeur dun attribut
).
La REF _Ref38691217 \h Figure 6 illustre un exemple de diagramme détats. Létat ordered est létat initial. Si le nombre de réservations est nul et que lopération deliver est exécutée, létat courant passe à available. Les états available et reserved sont regroupés dans un état composite InLibrary.
Figure SEQ Figure \* ARABIC 5 Exemple de diagramme de séquence
Figure SEQ Figure \* ARABIC 6 Exemple de diagramme détats
Méthodologie pour une conception testable
Le succès de lapproche objet tient beaucoup à la notion de composant réutilisable. La réutilisation de composants logiciels devient un enjeu crucial tant pour éviter de réécrire inutilement du logiciel, que pour réduire les coûts de développement. On cherche alors à développer la notion de composant objet « sur étagère », et il faut pour cela pouvoir mesurer la confiance que lon peut avoir en un composant.
Nous introduisons ici une méthode pour la conception de composants fiables fondée sur le test. Cette méthode considère un composant comme lagrégation dune implantation, dune spécification et dun ensemble de cas de test. Lidée consiste alors à considérer quon peut avoir confiance dans un composant si ces trois aspects sont cohérents lun vis-à-vis de lautre. Pour vérifier cette cohérence, nous rendons la spécification partiellement exécutable grâce à des contrats embarqués dans le composant, et nous utilisons lanalyse de mutation pour vérifier la cohérence entre les trois aspects.
Nous commençons par présenter les principes de la conception par contrat, et comment cette méthode peut être appliquée avec UML. Nous détaillons ensuite la méthode proposée par Triskell pour concevoir des composants fiables appelés composants autotestables.
La conception par contrat
La notion de contrat a été définie pour capturer les droits et obligations de chaque classe. Lexpérience montre que le simple fait dexprimer ces contrats de manière non ambiguë est une approche de conception valide ADDIN EN.CITE Jézéquel1997770Jézéquel, Jean-MarcMeyer, Bertrand1997Design by Contract: The lessons of ArianeComputer301129 - 130January[Jézéquel''97]. Bertrand Meyer a nommé cette approche de la construction de logiciel la conception par contrats (Design by Contract) ADDIN EN.CITE Meyer1992370Meyer, Bertrand1992Applying Design by ContractIEEE Computer251040 - 51OctoberZ:\biblio-bb\soft-contracts\ApplyingDbC.pdf[Meyer''92b]. Cette approche repose sur les théories des fonctions partielles et de la logique des prédicats. Elle offre aussi une méthodologie pour construire des systèmes modulaires, extensibles et réutilisables tout en prenant en compte la robustesse du système.
La conception par contrats est à lopposé de la programmation défensive qui recommande de mettre autant dassertions que possible dans le code ADDIN EN.CITE Liskov1986781Liskov, BarbaraGuttag, John1986Abstraction and Specification in Program DevelopmentMIT Press/Mc Graw Hill[Liskov''86]. La programmation défensive complique la localisation dune faute parmi les différents modules et augmente la complexité du logiciel ce qui peut entraîner des erreurs supplémentaires.
La conception par contrats conduit les développeurs à bien séparer les responsabilités de lappelant dune méthode (le client) et de limplantation de cette méthode. Ces contrats sont définis par des expressions booléennes appelées précondition et postcondition pour les routines. On peut aussi définir un invariant de classe pour définir les propriétés globales de loccurrence dune classe qui doivent être vérifiées par toutes les routines. Un contrat définit les droits et obligations de chaque partie : le client doit appeler une méthode uniquement en respectant la précondition de la méthode et linvariant de la classe à laquelle elle appartient. En échange, la méthode appelée garantit que la propriété définie par la postcondition ainsi que linvariant de classe sont vérifiés.
Si un contrat nest pas respecté, une erreur a été détectée. La violation dune précondition signifie que le client na pas respecté le contrat : dans ce cas, la méthode appelée ne peut pas respecter sa part du contrat mais peut signaler la faute en soulevant une exception. La violation dune postcondition signale une erreur dans limplantation de la méthode appelée qui ne peut pas respecter sa part du contrat.
La conception par contrats est intégrée au système de typage des langages orientés objet grâce à la notion de sous-contrat fournie par le mécanisme dhéritage. En effet, la liaison dynamique permet à une routine de passer un contrat avec une redéfinition pour son implantation. La redéfinition est alors une transformation qui préserve la sémantique puisque la nouvelle définition doit au moins remplir le contrat de la méthode originale, et éventuellement en faire plus (cest à dire quelle peut accepter des appels qui nétaient pas acceptés par loriginale, et fournir un «meilleur » résultat). Cest pourquoi une précondition dans une sous-classe peut-être affaiblie (accepter plus), et une postcondition renforcée (faire plus).
Dans la suite de cette thèse, nous nous intéressons à lapport qualitatif de la conception par contrat. Nous étudions comment et à quel moment dans le développement les contrats permettent daméliorer la qualité dun composant. Par exemple, de nombreux travaux soulignent limportance des contrats pour la détection et la localisation des erreurs. Dans le chapitre REF _Ref34906822 \r \h 3, nous proposons un modèle pour évaluer lapport des contrats pour le diagnostic (phase de localisation des erreurs), en fonction de leur qualité et de leur répartition dans le système. Nous étudions aussi lapport des contrats pour la détection derreur en proposant une mesure de la robustesse dun système orienté-objet.
Les contrats et UML : le langage OCL
Le langage OCL (Object Constraint Language, ADDIN EN.CITE OMG199711616OMG1997Object Constraint Language Specification2002http://www.omg.org/docs/ad/97-08-08.pdf[OMG''97]) est un langage formel pour exprimer des contraintes sur des diagrammes UML. Il permet de décrire un invariant pour une classe, et des pré et post conditions pour des méthodes. OCL permet donc de décrire des contrats sur un modèle UML, et sera utilisé dans la suite pour illustrer certains points lors de létude de la qualité des contrats pour un composant ou un assemblage de composants.
Figure SEQ Figure \* ARABIC 7 Diagramme de classes pour une banque
Dans la suite de cette section, nous illustrons les différents types de contraintes OCL, ainsi que leur syntaxe, sur lexemple de la REF _Ref32479505 \h Figure 7. Un invariant peut être exprimé pour chacune des classes Bank et BankAccount. Une contrainte de type invariant est désignée par le mot-clé inv, et le type sur lequel la contrainte doit être appliquée est désigné par le mot-clé context. Par exemple, un invariant pour la classe BankAccount, spécifiant que la valeur de balance doit supérieure ou égale à la valeur de overdraft, pour tout instance de BankAccount, sécrit de la manière suivante :
context bankAccount inv:
self.balance >= self.overdraft
OCL permet de naviguer les associations à partir dun objet particulier, le nom du rôle désigne lensemble des objets de lautre côté de lassociation. Par exemple, un invariant qui contraint toute instance de Bank à avoir au moins un compte sécrit de la manière suivante :
context Bank inv :
self.accounts->size>0
Les contraintes de type pré et post condition sont désignées par les mots-clé pre et post dans OCL. Dans les postconditions, OCL permet daccéder à la valeur de létat avant lopération grâce à lopérateur @pre. Une précondtion pour la méthode deposit de la classe BankAccount consiste à vérifier que le montant passé en paramètre est positif. Une postcondition vérifie que la valeur de balance après lopération est égale à la valeur avant lopération plus le montant passé en paramètre. Ces contraintes sexpriment de la manière suivante :
context BankAccount::deposit(sum:real):void
pre sum > 0
post self.balance = self.balance@pre + sum
Les composants autotestables
La solution envisagée dans le cadre du projet Triskell, pour la conception de composants de confiance, consiste à considérer chaque composant comme un tout possédant une spécification, une implantation et un ensemble de cas de test mémorisés et pouvant être activés ( REF _Ref31014415 \h Figure 8). Des composants conçus avec cette méthode sont dits autotestables ADDIN EN.CITE Jézéquel200180Jézéquel, Jean-MarcDeveaux, D.Le Traon, Yves2001Reliable Objects: a Lightweight Approach Applied to JavaIEEE Software18476 - 83July/August[Jézéquel''01]. Cette approche conceptuelle est généralisable à des échelles plus grandes que le composant : des assemblages de composants autotestables sont autotestables. A tous les niveaux de complexité, les composants ont la possibilité dexécuter les cas de test qui leur sont associés.
Avec la conception par contrat ADDIN EN.CITE Meyer1992311Meyer, Bertrand1992Object-oriented software constructionPrentice Hall1254Jézéquel1999211Jézéquel, Jean-MarcTrain, MichelMingins, Christine1999Design Patterns and ContractsAddison-Wesley3480-201-30959-9[Meyer''92a; Jézéquel''99], des contrats exécutables sont dérivés de la spécification. De cette manière, les trois aspects du composant autotestable sont exécutables, et peuvent être confrontés lun à lautre et améliorés de manière incrémentale pour augmenter la confiance dans le composant.
Un point important lors de la conception de composants logiciels destinés à être utilisés dans différents environnements, est dêtre capable de leur associer une mesure de confiance. Le terme anglais « trustability » ADDIN EN.CITE Howden1970363Howden, William E.Huang, Yudang1970Software TrustabilityIEEE Symposium on Adaptive processes- Decision and Control[Howden''70], désigne cette propriété quil est difficile destimer directement : on ne peut lévaluer quen analysant certains facteurs qui influencent cette propriété. Dans le cadre de la méthode des composants autotestables, nous considérons la qualité de lensemble des cas de test associé au composant comme le facteur principal pour lévaluation de la confiance. En effet, un ensemble efficace de cas de test doit permettre daméliorer limplémentation et les contrats, et dobtenir ainsi une forte cohérence entre ces aspects, ce qui permet davoir confiance de manière indirecte dans le composant.
Figure SEQ Figure \* ARABIC 8 Composant autotestable la vue en triangle
Méthodologiquement, on souhaite donc renforcer la cohérence entre les trois facettes dun composant (spécification/implantation/cas de test) en les confrontant lun à lautre. Une fois quun ensemble de cas de test efficaces est disponible, la correction des erreurs détectées améliore la facette implantation. Pour lamélioration de la partie spécification exprimée à laide de contrats, lidée est la suivante : si les contrats sont complets, ils devraient être violés quand une erreur est présente dans limplantation, et quun cas de test exécute la partie erronée, et déclenche lerreur. De plus si les contrats sont corrects, ils ne devraient être violés que dans ces cas-là. Donc, si on exécute un ensemble de cas de test efficaces sur une version du programme dans laquelle on a placé une erreur, et quaucun contrat nest violé, alors une faiblesse dans les contrats a été détectée. Les contrats qui auraient dû détecter lerreur doivent alors être complétés. Dautre part, lorsque lensemble de cas de test est exécuté sur limplantation et quun contrat est violé, si aucune erreur nest trouvée dans le programme, il faut vérifier que ce nest pas dû à une erreur dans un contrat.
Un critère pour évaluer lefficacité dun ensemble de cas de test est donc nécessaire à lapplication de cette méthodologie. Nous aurions pu, dans un premier temps, recourir à un critère de couverture (type couverture de code), mais cela ne reflèterait que de manière très lointaine la capacité des cas de test à révéler des erreurs ADDIN EN.CITE Voas1992260Voas, Jeffrey M.Miller, K.1992The Revealing Power of a Test CaseSoftware Testing, Verification and Reliability2125 - 42May[Voas''92a]. Dans cette optique, la technique certes plus lourde qui nous a paru la plus proche de lactivité effective du test, est lanalyse de mutation, à condition de ladapter aux langages OO et de permettre le passage à léchelle (test dun assemblage de composants).
Lanalyse de mutation est la technique centrale qui a été choisie pour tout ce processus damélioration, et elle présentée en détail dans la section REF _Ref37241324 \r \h 2.1.3. Bien que ce soit avant tout une technique pour améliorer et valider un ensemble de cas de test, nous venons dévoquer comment elle pourrait être utilisée pour valider les contrats.
La conception de composants autotestables permet aussi de résoudre des problèmes de test dans le cas de lévolution et de la réutilisation de composants. En effet, comme les cas de test sont une partie intégrante du composant, il porte la capacité de se tester (autotest), et ses cas de test peuvent être appelés depuis le système. Trois cas de figures peuvent être pris en compte pour un composant C dans un système S :
modification de limplémentation de C : lautotest permet de tester la nouvelle implémentation, la spécification ne change pas,
ajout de nouvelles fonctionnalités à C : lautotest doit être lancé pour assurer le test de non-régression, puis lensemble de cas de test et la spécification doivent être complétés pour prendre en compte les nouvelles fonctionnalités. Enfin, tous les composants clients de C doivent sautotester pour garantir la non-régression.
réutilisation du composant dans un système S : lors de lajout dun composant C dans un système, lautotest de C doit être lancé depuis le système pour vérifier sa bonne intégration (héritage et clientèle).
Lapproche développée apporte donc un support pour aider à résoudre le problème du test dintégration, du test de non-régression, de lévolution des composants et de leur réutilisation. Elle a pour principale limitation le fait quelle ne permet pas le test fonctionnel du système, et quelle ne modélise pas explicitement les aspects comportementaux, dynamiques du système. Cest ce dernier aspect, quil faudrait prendre en compte de manière explicite (aspects de communication entre objets) en proposant des modèles et des méthodes de test spécifiques.
Conclusion
Au cours de cette section, nous avons présenté la conception par contrats. Cette technique est utilisée pour rendre une partie de la spécification exécutable, ce qui permet de confronter limplantation et la spécification grâce au test dynamique. Le test permet donc daméliorer la cohérence entre ces deux aspects dun composant et ainsi daugmenter la confiance dans ce composant. Nous avons présenté une méthode proposée par léquipe Triskell, et fondée sur la cohérence entre les cas de test, limplantation et la spécification dun composant, pour assurer un certain niveau de confiance dans le composant.
Les trois sections suivantes détaillent létat de lart spécifique aux trois volets de cette thèse.
La génération automatique de cas de test
Nous détaillons ici les principaux travaux sur la génération automatique de cas de test, travaux qui sont en lien avec le chapitre REF _Ref38366760 \r \h 3 dans lequel nous exposons notre contribution à la génération de test pour des composants.
La génération automatique de données de test est un problème crucial pour la construction de logiciels fiables ADDIN EN.CITE Fewster19991891Fewster, MarkGraham, Dorothy1999Software Test AutomationAddison-Wesley0-201-33140-3Dustin19991881Dustin, ElfriedeRashka, JeffPaul, John1999Automated Software TestingAddison-Wesley0-201-43287-0[Dustin''99; Fewster''99]. Lefficacité de la phase de test dépend en grande partie de la pertinence de données utilisées. Les techniques pour la génération automatique sont de deux types : fonctionnelle ou structurelle.
Techniques fonctionnelles
Les techniques de génération fonctionnelle se fondent sur une spécification du comportement du programme. Il existe un ensemble de travaux sur la génération automatique de cas de test à partir dune spécification formelle dun système. Ces travaux sappuient sur la méthode présentée dans ADDIN EN.CITE Dick19932093Dick, J.Faivre, A.1993Automating the Generation and Sequencing of Test Cases from Model-based SpecificationsFME'93268 - 284April[Dick''93] qui consiste à générer un automate détats finis à partir de la spécification formelle, puis à sélectionner des cas de test comme des chemins dans lautomate. Dans ADDIN EN.CITE Legeard20022083Legeard, BrunoPeureux, FabienUtting, Mark2002Automated Boundary Testing from Z and BFormal Methods (FME'02)Copenhagen, Denmark21 - 40July[Legeard''02], les auteurs utilisent une spécification B ou Z du programme à tester. Ces spécifications sont ensuite traduites au format BZ-Prolog proposé par les auteurs, chaque opération est alors décrite par son nom, les noms et types des paramètres dentrée et de sortie et les pré et post conditions. À partir dune telle description, Legeard et al. décrivent une technique pour générer automatiquement des données de test aux limites, ainsi que les préambule et postambule nécessaires à lexécution du cas de test. Dans ADDIN EN.CITE Murray19982103Murray, L.Carrington, D.MacColl, I.McDonald, J.Strooper, P.1998Formal Derivation of Finite State Machines for Class TestingZUM '98: The {Z} Formal Specification NotationBerlin, Germany42-59SeptemberD:\Travail\biblio-bb\TestGeneration\DerivationStateMachinesClass.ps[Murray''98], Murray et al. utilisent une spécification Object-Z dune classe pour générer un automate et générer des cas de test unitaires pour la classe. Sur la même idée, les auteurs de ADDIN EN.CITE Grieskamp20022113Grieskamp, WolfgangGurevich, YuriSchulte, WolframVeanes, Margus2002Generating Finite State Machines from Abstract State MachinesISSTA'02 (International Symposium on Software Testing and Analysis)Rome, Italy112-122JulyD:\Travail\biblio-bb\TestGeneration\AbstractStateMachines.pdf[Grieskamp''02], dérivent un automate détats fini à partir dune machine à états abstraite pour générer des cas de test.
Un autre exemple de génération automatique de cas de test fondée sur une spécification formelle est décrit dans ADDIN EN.CITE Marinov20011593Marinov, DarkoKhurshid, Sarfraz2001TestEra: A Novel Framework for Automated Testing of Java ProgramsASE'01 (Automated Software Engineering)San Diego, CA, USA22 - 31NovemberZ:\biblio-bb\TestGeneration\automaticGenrationTestForjava.pdf[Marinov''01]. Ici, les auteurs utilisent Alloy, un langage qui permet de spécifier le comportement de méthodes Java. Dautres approches se basent sur lexpression des exigences pour la génération de tests fonctionnels ADDIN EN.CITE Tahat20011533Tahat, L.H.Vaysburg, B.Korel, B.Bader, A.J.2001Requirement-based automated black-box test generationCOMPSACChicago, IL, USA489 - 495October[Tahat''01] ou sur la description de scénarios abstraits ADDIN EN.CITE Tsai20011823Tsai, Wei-TekBai, XiaoyingPaul, R.Yu, Lian2001Scenario-based functional regression testingCOMPSAC 2001Chicago, IL, USA496 - 501OctoberZ:\biblio-bb\TestGeneration\ScenarioBased.pdfNebut20021673Nebut, ClémentinePickin, SimonLe Traon, YvesJézéquel, Jean-Marc2002Reusable Test Requirements for UML-Modeled Product LinesREPLEssen, Germany51 - 56SeptemberZ:\biblio-bb\TestOO\UMLbased\TestPattern.pdf[Tsai''01; Nebut''02].
Dans ADDIN EN.CITE Pickin20021683Pickin, SimonJard, ClaudeLe Traon, YvesJéron, T.Jézéquel, Jean-MarcLe Guennec, Alain2002System Test Synthesis from UML Models of Distributed SoftwareFORTEZ:\biblio-bb\TestOO\UMLbased\UML-TGV.pdf[Pickin''02] les auteurs décrivent une solution fondée sur une spécification formelle du système. Cette approche prend en entrée un diagramme de classes, les machines à états décrivant le comportement de chaque classe, un déploiement initial du système et un diagramme de séquence décrivant un objectif de test. A partir de cette spécification, un simulateur intégré à latelier de génie logiciel UMLAUT ADDIN EN.CITE Ho19991803Ho, Wai-MingJézéquel, Jean-MarcLe Guennec, AlainPennaneac'h, François1999UMLAUT: an Extendible UML Transformation FrameworkASE'99 (Automated Software Engineering)Cocoa Beach, Florida, USA275 - 278October[Ho''99] peut générer un graphe étiqueté décrivant le comportement global du système, et un graphe pour lobjectif de test. Ces deux graphes sont donnés en entrée de loutil TGV ADDIN EN.CITE Jéron19981813Jéron, T.Morel, P.1998Test Generation Derived from Model-checkingCAV'99Kyoto, Japan108 - 122July[Jéron''98] qui génère un cas de test exécutable sur limplantation du système.
Dautres travaux sur la génération automatique de test fondée sur les modèles UML ont été réalisés dans le cadre du projet européen AGEDIS ADDIN EN.CITE AGEDIS200021216AGEDIS2000Automated Generation and Execution of Test Suites for DIstributed Component-based Software2003http://www.agedis.de/index.shtml[AGEDIS''00]. Ici, les techniques étudiées utilisent le diagramme de classes, des diagrammes dobjets pour spécifier des situations initiales dutilisation du système, et les diagrammes détats pour spécifier le comportement dynamique des objets. A partir de ces différents éléments de spécification une méthode ainsi que des outils pour la génération automatique de cas de test ont été proposés.
Techniques structurelles
Parmi les techniques structurelles, on distingue la génération statique de la génération dynamique. La génération statique consiste à générer des données de test sans exécuter le programme sous test. Des outils de génération statique sont fondés sur la résolution de contraintes sur les données en entrée. Un exemple dune telle technique est lexécution symbolique de programme ADDIN EN.CITE Clarke19761830Clarke, L. A.1976A system to generate test data and symbolically execute programsIEEE Transactions on Software Enginnering23215 - 22September[Clarke''76], qui consiste à donner une valeur symbolique à chacune des variables du programme. Lexécution statique du programme permet ensuite dobtenir une expression représentant une contrainte sur les variables, nécessaire pour atteindre un point du programme. Dans ADDIN EN.CITE Lugato20021840Lugato, DavidBigot, CélineValot, Yannick2002Validation and automaitc test generation on UML models: the AGATHA approachElectronic Notes in Theoretical Computer Science662December[Lugato''02], les auteurs utilisent AGATHA, un outil fondé sur lexécution symbolique, pour générer des données de test à partir dune spécification avec UML.
La génération dynamique de cas de test initialement proposée dans ADDIN EN.CITE Miller1976980Miller, Webb Spooner, David L.1976Automatic Generation of Floating-Point Test DataIEEE Transactions on Software Enginnering23223 - 226September[Miller''76] consiste à instrumenter le code du programme sous test pour collecter des informations sur le programme au cours de lexécution dun cas de test. Ces informations sont utilisées pour évaluer la qualité du cas de test par rapport à un critère donné. Les données de test sont ensuite modifiées pour tenter daméliorer le cas de test afin quil satisfasse entièrement le critère choisi. De nombreux travaux sur la génération dynamique de cas test sont inspirés des travaux de Korel qui ramène ce problème à un problème de minimisation de fonction ADDIN EN.CITE Korel19901000Korel, B.1990Automated Software Test Data GenerationIEEE Transactions on Software Enginnering168870 - 879AugustZ:\biblio-bb\TestGeneration\AutTestDataGene.pdfKorel19921540Korel, B.1992Dynamic method for software test data generationSoftware Testing, Verification and Reliability24203 - 213DecemberKorel19961523Korel, B.Al-Yami, A.M.1996Assertion-oriented automated test data generationICSEBerlin, Germany71 - 80MarchZ:\biblio-bb\TestGeneration\AssertionOrientedTestGene.pdf[Korel''90; Korel''92; Korel''96]. Lidée est la suivante : un objectif de test peut sexprimer sous la forme dune fonction sur les données en entrée, trouver une donnée de test pertinente consiste alors à trouver une donnée qui minimise la fonction. Korel utilise la descente de gradient pour résoudre la minimisation de fonction. Un grand nombre de chercheurs ont utilisé un algorithme génétique pour résoudre ce problème ADDIN EN.CITE Wegener20011580Wegener, JoachimBaresel, AndreStahmer, Harmen2001Evolutionary Test Environment for Automatic Structural TestingInformation and Software Technology4314841 - 854DecemberZ:\biblio-bb\TestGeneration\EvolutionaryTestGeneration.pdfBoden19961713Boden, E.B.Martino, G.F.1996Testing software using order-based genetic algorithmsGenetic Programming 1996 ConferenceStanford, CA, USA461 - 466JulySchultz19931720Schultz, A.C.Grefenstette, J.J.De Jong, K.A.1993Test and Evaluation by Genetic AlgorithmsIEEE Expert859 - 14OctoberJones19981020Jones, B. F.Eyres, D. E.Sthamer, H.-H.1998A Strategy for using Genetic Algorithms to Automate Branch and Fault-based TestingThe Computer Journal41298 - 107Z:\biblio-bb\TestGeneration\GABranchFault&BasedTesting.pdfJones1996440Jones, B. F.Sthamer, H.-H.Eyres, D. E.1996Automatic Structural Testing Using Genetic AlgorithmsSoftware Engineering Journal115299 - 306SeptemberTracey19981743Tracey, NigelClark, JohnMander, KeithMcDermid, J.A.1998An automated framework for structural test-data generationASEHonolulu, HI, USA285 - 288OctoberZ:\biblio-bb\TestGeneration\AutomatedFramework.pdfXanthakis19921753Xanthakis, S.Ellis, C.Skourlas, C.Le Gall, A.Katsikas, S.Karapoulios, K.1992Genetic Algorithms Applications to Software TestingFifth International Conference. Software Engineering and Its ApplicationsToulouse, France625 - 636DecemberMichael2001700Michael, Christoph C.McGraw, GarySchatz, Michael A.2001Generating Software Test Data by EvolutionIEEE Transaction on Software Engineering27121085 - 1110DecemberZ:\biblio-bb\TestOO\GAbasedTestGeneration.pdfTracey20001010Tracey, NigelClark, JohnMander, KeithMcDermid, John2000Automated test-data generation for exception conditionsSoftware Practice and Experience30161 - 79JanuaryZ:\biblio-bb\TestGeneration\AutTestDataGeneForExceptions.pdfPargas1999610Pargas, Roy Harrold, Mary Jean Peck, Robert1999Test-Data Generation Using Genetic AlgorithmsJournal of Software Testing, Verifications, and Reliability9263 - 283SeptemberZ:\biblio-bb\TestGeneration\TestGeneration&GA.pdf[Xanthakis''92; Schultz''93; Boden''96; Jones''96; Jones''98; Tracey''98; Pargas''99; Tracey''00; Michael''01; Wegener''01]. Ces travaux, que nous détaillons dans la suite, diffèrent par la modélisation des prédicats comme fonction des variables dentrée.
Dans ADDIN EN.CITE Michael2001700Michael, Christoph C.McGraw, GarySchatz, Michael A.2001Generating Software Test Data by EvolutionIEEE Transaction on Software Engineering27121085 - 1110DecemberZ:\biblio-bb\TestOO\GAbasedTestGeneration.pdf[Michael''01], Michael et al. présentent leur outil GADGET (Genetic Algorithm Data GEneration Tool) qui génère des données de test scalaire. Les individus sont modélisés comme une chaîne de bits correspondant à la représentation binaire de la donnée de test et la fonction dutilité correspond à la fonction quon cherche à minimiser. Larticle détaille la modélisation du problème et donne des résultats expérimentaux avec plusieurs petits programmes (30 lignes de code) et un programme plus important (2000 lignes de code). Au cours de ces expériences, les auteurs ont utilisé deux types dalgorithme génétique pour la génération de données de test. Les résultats sont comparés à la génération aléatoire, et à lutilisation de la descente de gradient pour la minimisation de fonction. La génération purement aléatoire est beaucoup moins efficace que les autres techniques dès que la taille du programme sous test devient importante, ce qui justifie leffort supplémentaire nécessaire à lapplication des méthodes plus sophistiquées. Parmi les trois algorithmes de génération, lalgorithme génétique classique est le plus efficace dans une large majorité des cas.
Tracey et al. ADDIN EN.CITE Tracey20001010Tracey, NigelClark, JohnMander, KeithMcDermid, John2000Automated test-data generation for exception conditionsSoftware Practice and Experience30161 - 79JanuaryZ:\biblio-bb\TestGeneration\AutTestDataGeneForExceptions.pdf[Tracey''00] utilisent un algorithme génétique pour la génération de données de test pour les conditions dexception. Ils présentent le calcul de la fonction dutilité et détaillent un exemple. Ils donnent ensuite des résultats expérimentaux pour des programmes écrits en Ada95. Le plus gros exemple est un système de contrôle pour un moteur davion, pour lequel lalgorithme est capable de générer des données de test qui soulèvent chaque exception possible.
Jones et al. ADDIN EN.CITE Jones19981020Jones, B. F.Eyres, D. E.Sthamer, H.-H.1998A Strategy for using Genetic Algorithms to Automate Branch and Fault-based TestingThe Computer Journal41298 - 107Z:\biblio-bb\TestGeneration\GABranchFault&BasedTesting.pdfJones1996440Jones, B. F.Sthamer, H.-H.Eyres, D. E.1996Automatic Structural Testing Using Genetic AlgorithmsSoftware Engineering Journal115299 - 306September[Jones''96; Jones''98] donnent différents calculs possibles pour la fonction dutilité. Pour tous ces modèles, les prédicats sont vus comme une fonction des données en entrée. Il faut ensuite trouver des données de test qui minimisent ces fonctions. Dans ADDIN EN.CITE Jones19981020Jones, B. F.Eyres, D. E.Sthamer, H.-H.1998A Strategy for using Genetic Algorithms to Automate Branch and Fault-based TestingThe Computer Journal41298 - 107Z:\biblio-bb\TestGeneration\GABranchFault&BasedTesting.pdf[Jones''98], les mêmes auteurs proposent dutiliser aussi le score de mutation comme fonction dutilité.
Pargas et al. ADDIN EN.CITE Pargas1999610Pargas, Roy Harrold, Mary Jean Peck, Robert1999Test-Data Generation Using Genetic AlgorithmsJournal of Software Testing, Verifications, and Reliability9263 - 283SeptemberZ:\biblio-bb\TestGeneration\TestGeneration&GA.pdf[Pargas''99] utilisent aussi un algorithme génétique pour la génération dynamique de données de test. Ici, la fonction dutilité est fondée sur la couverture du graphe de dépendance de contrôle (GDC). Les auteurs ont développé un prototype appelé TGen. Loutil génère une donnée de test pour un objectif de test. Cet objectif correspond à un chemin du GDC que lon veut couvrir. En exécutant le cas de test sur une version instrumentée du programme sous test, TGen repère les nuds couverts par le cas de test. La valeur dutilité du cas de test est évaluée par le nombre de nuds en commun entre le chemin couvert et le chemin correspondant à lobjectif de test. Larticle décrit précisément le processus de génération fondé sur lalgorithme génétique, puis plusieurs expériences sur 6 petits programmes (entre 32 et 82 lignes de code). Les résultats de lalgorithme génétique sont comparés aux résultats obtenus avec un générateur aléatoire.
Tous les travaux sur les algorithmes génétiques présentés ici offrent des solutions quil est difficile dadapter dans un cadre orienté objet. Tout dabord, ils ont en commun de générer des données de type simple, ce qui leur permet de modéliser la solution sous forme dune chaîne binaire. Or, dans le cas de programmes orientés objet, nous devons générer des objets complexes quil est difficile de modéliser sous forme binaire. Par ailleurs, ces travaux exécutent lalgorithme génétique pour chaque objectif de test, cest-à-dire quils ne génèrent quune donnée de test à chaque exécution. Dans le cadre de la méthodologie nous voulons que lalgorithme optimise un ensemble de cas de test pour un composant, cest-à-dire quil génère toutes les données nécessaires pour tester correctement le composant en une seule exécution.
Dans le chapitre REF _Ref37421767 \r \h 3, nous étudions donc un algorithme génétique pour loptimisation et la génération automatique de cas de test, et proposons dadapter cet algorithme pour résoudre les problèmes soulevés ci-dessus. Ces adaptations donnent lieu à un nouvel algorithme semi-aléatoire que nous baptisons algorithme bactériologique.
Conception par contrat et test
Dans cette thèse nous insistons sur lintérêt des contrats tant pour la qualité dun composant que pour assurer une qualité de lassemblage (cest le thème du chapitre REF _Ref38366775 \r \h 4). Pour avoir une idée de limpact des contrats sur cette qualité nous proposons une étude de lapport dune conception par contrat ADDIN EN.CITE Meyer1992370Meyer, Bertrand1992Applying Design by ContractIEEE Computer251040 - 51OctoberZ:\biblio-bb\soft-contracts\ApplyingDbC.pdf[Meyer''92b] pour la robustesse et la diagnosabilité dun système orienté objet. Au cours de ces travaux nous avons étudié des méthodes pour lécriture des contrats, essayé de distinguer différentes qualités pour ces contrats en vue dune classification, et enfin nous nous sommes intéressés aux contrats pour aider le test de logiciel. Cette section dresse létat de lart sur différents points : les méthodes pour la conception par contrat, la classification des contrats et enfin, les contrats et plus généralement, les assertions, pour le test de logiciel.
Plusieurs articles traitent du problème de la rédaction des contrats, en se concentrant sur différents aspects de cette rédaction pour proposer une méthode ou une classification. Le but de tous ces travaux est de proposer une aide pour écrire des contrats de manière efficace.
Les auteurs de ADDIN EN.CITE Collet1996223Collet, Philippe Rousseau, Roger1996Assertions are Objects too!First Intern. Conf. on Object-Oriented TechnologySt Petersburgh, RussiaJune[Collet''96] proposent une classification des contrats sur deux critères, dordre plus méthodologique que syntaxique. Le premier critère prend en compte les entités utilisées par les contrats, pour aider à placer les contrats de manière efficace. Le second critère se réfère à lintention du programmeur, c'est-à-dire à la granularité jugée nécessaire par le programmeur. David Rosenblum propose aussi une classification empirique des contrats dans ADDIN EN.CITE Rosenblum1995330Rosenblum, David S.1995A Practical Approach to Programming With AssertionsIEEE Transactions on Software Enginnering21119 - 31January[Rosenblum''95]. Les critères choisis dans cet article sont spécifiques au langage C, et le but est de classifier les types derreur détectée par les contrats, plus que de fournir une méthode de rédaction de ces contrats.
Dautres équipes se sont intéressées à des règles minimales pour la rédaction de contrats efficaces. McKim ADDIN EN.CITE McKim1995323McKim, James1995Class Interface Design and Programming by ContractTOOLS 17 (Technology of Object Oriented Languages and Systems)Englewood CliffsPrentice-Hall395 - 419[McKim''95] établit une liste minimale de points qui doivent être vérifiés lors dune conception par contrats. Cette liste est complétée dans ADDIN EN.CITE Mitchell1999233Mitchell, RichardMcKim, James1999Extending a method of devising software contractsMingins, ChristineMeyer, BertrandTools'32[Mitchell''99], à partir de lobservation de lapplication du design pattern Observateur ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95], les auteurs de cette étude repèrent des problèmes qui apparaissent lors de la rédaction de contrats pour un ensemble de classes plutôt que pour une classe isolée. Le but de ces travaux est daméliorer les performances, et lextensibilité des composants.
Concernant les méthodes pour utiliser les contrats pour le développement dun logiciel, Nordby, Blom et Brunstrom présentent une méthode de développement de logiciel fondée sur les contrats dans ADDIN EN.CITE Nordby2002243Nordby, J. EivindBlom, MartinBrunstrom, Anna2002On the Relation between Design Contracts and Errors: a Software Development StrategyECBSLund, SwedenApril[Nordby''02]. Ils distinguent des contrats forts et des contrats faibles, qui correspondent à deux niveaux de granularité : les contrats forts établissent des obligations que le client doit vérifier pour utiliser la routine, la post-condition se contente alors dexprimer le résultat dans le cas où la routine est utilisée correctement. Pour un contrat faible, la pré-condition sera typiquement à vrai, relâchant ainsi les contraintes sur le client. La post-condition devra alors exprimer le résultat attendu dans le cas dune utilisation correcte et dune utilisation incorrecte (message derreur, exception
). Les deux types de contrat sont utilisés à des moments différents du développement : les contrats forts pour le développement des unités et la découverte derreurs internes, les contrats sont ensuite affaiblis au moment de lintégration pour tester la robustesse du composant. Dans ADDIN EN.CITE Nordby2002243Nordby, J. EivindBlom, MartinBrunstrom, Anna2002On the Relation between Design Contracts and Errors: a Software Development StrategyECBSLund, SwedenApril[Nordby''02], les auteurs présentent une manière sûre daffaiblir des contrats, et donnent des résultats dapplication de cette méthode dans un cadre industriel. Les contrats forts ont permis de détecter rapidement les erreurs au cours du développement de deux composants. Les contrats ont ensuite été affaiblis, et cette opération pour passer des contrats forts aux contrats faibles na pas introduit derreur.
De manière générale, la conception contrats peut être vue comme une méthode particulière pour placer des assertions dans un programme. Lorsque ces assertions sont exécutables, elles fournissent un moyen puissant de détection derreurs. Cependant, peu de travaux se sont intéressés aux assertions pour tester un programme.
Les assertions placées dans le code dun programme peuvent être utilisées comme fonction doracle pour le test. Elles vérifient la cohérence de létat du logiciel à certains points précis. La violation dune assertion signifie donc que le programme est passé dans un état erroné, et quune opération a rendu un résultat en contradiction avec sa spécification. Dans ADDIN EN.CITE Fenkam20021153Fenkam, PascalGall, HaraldJazayeri, Mehdi2002Constructing Corba-Supported Oracles for Testing: A Case Study in Automated Software TestingASE'02 (Automated Software Engineering)Edimburgh, UK129 - 138September[Fenkam''02] les auteurs étudient la dérivation dassertions pour loracle de test de composants CORBA à partir dune spécification formelle du programme. Les auteurs de ADDIN EN.CITE Cheon20021043Cheon, YoonsikLeavens, Gary T.2002A Simple and Practical Approach to Unit Testing: The JML and JUnit WayECOOPMalaga, Spain2374231 - 255Springer-VerlagLecture Notes in Computer ScienceJuneZ:\biblio-bb\TestOO\UnitTestingJML&JUnit.pdf[Cheon''02] utilisent les post-conditions de méthodes et les invariants de classe exprimés avec JML (Java Modeling Language) pour générer des oracles pour le test de classes Java. JML est un langage pour écrire des pré et post conditions et des invariants pour des programmes écrits en Java. Les oracles sont automatiquement générés dans une classe au format JUnit. Edwards propose aussi dutiliser les assertions embarquées dans le logiciel comme oracle pour le test de composants logiciels ADDIN EN.CITE Edwards20011480Edwards, S. H.2001A Framework for Practical, Automated Balck-Box Testing of Component-Based SoftwareSoftware Testing, Verification and Reliability11297 - 111JuneZ:\biblio-bb\TestGeneration\AutomatedTestingForCompBasedSoft.pdf[Edwards''01], sans cependant étudier leur efficacité ou leur complétude.
Un autre aspect du test pour lequel les assertions savèrent utiles, est la testabilité. La testabilité est un facteur de qualité qui exprime la difficulté pour tester un programme (tant du point de vue de leffort de test que de la difficulté de détecter les erreurs). Un des points essentiels, pour découvrir des erreurs au cours du test, est de pouvoir observer le comportement du programme à différents moments de lexécution. En effet, si on observe une erreur au milieu de lexécution, on sait que lerreur se trouve dans la partie du programme exécutée avant ce moment, alors que si on nobserve lerreur quà la fin, la localisation de lerreur est plus difficile. Voas a beaucoup étudié ces problèmes dobservabilité, et propose une méthode pour placer des assertions de manière efficace pour améliorer la testabilité du programme ADDIN EN.CITE Voas19991060Voas, J. M.Kassab, Lora1999Using Assertions to Make Untestable Software More TestableSoftware Quality Professional14SeptemberZ:\biblio-bb\Testabilité\AssertionsForTestability.pdf[Voas''99].
Enfin, Korel ADDIN EN.CITE Korel19961523Korel, B.Al-Yami, A.M.1996Assertion-oriented automated test data generationICSEBerlin, Germany71 - 80MarchZ:\biblio-bb\TestGeneration\AssertionOrientedTestGene.pdf[Korel''96] utilise les assertions pour générer des données de test automatiquement. Son idée est de générer des données en entrée qui violent des assertions dans le programme. Sil trouve de telles données, qui sont conformes à la spécification des données en entrée du programme sous test, elles ont forcément détecté une erreur qui peut être de lun de trois types suivants : une erreur dans le programme, une erreur dans une assertion, une pré-condition trop faible. Pour générer de telles données, il ramène le problème à celui de la génération de données de test qui atteignent un point particulier du programme et le résout comme dans ADDIN EN.CITE Korel19921540Korel, B.1992Dynamic method for software test data generationSoftware Testing, Verification and Reliability24203 - 213December[Korel''92].
En conclusion, il existe dune part des travaux sur des aspects précis de la rédaction de contrats pour un programme, et dautre part des travaux sur lutilisation des assertions exécutables pour aider le test. Cependant, il semble quaucune étude ne se soit consacrée sur limpact des contrats sur le test. Dans la suite de cette thèse (chapitre REF _Ref37421785 \r \h 4), nous proposons une évaluation de lapport des contrats pour la détection et la localisation derreurs.
Testabilité et mesures pour les logiciels OO
La testabilité, que nous étudions pour un assemblage de composants au chapitre REF _Ref38366799 \r \h 5, est à la frontière de deux domaines. D'une part, elle est liée aux problèmes de test en évaluant l'effort requis pour examiner un logiciel. D'autre part, la testabilité est une mesure. Ainsi, cette section présente les travaux existants sur laspect mesure de la testabilité, et introduit certains travaux plus généraux sur la mesure des logiciels OO.
La mesure de testabilité est un critère de qualité qui évalue, à partir de caractéristiques structurelles dun programme, leffort nécessaire pour le tester. Cette mesure peut être définie ainsi :
Testabilité. La testabilité est un facteur de qualité, défini comme la facilité à tester un logiciel. Cette facilité est à la fois une propriété intrinsèque du schéma de conception (et donc une caractéristique propre au produit étudié) et une propriété relative à la stratégie de test adoptée pour vérifier un critère de test particulier. La testabilité se dérive en trois attributs :
le coût global de test : le coût global pour obtenir des cas de test vérifiant un critère de test donné
la contrôlabilité : la facilité pour générer des données de test efficaces (propriété intrinsèque au logiciel sous test)
lobservabilité : la facilité avec laquelle il est possible de vérifier la pertinence des résultats obtenus après lexécution des cas de test
De manière générale, la testabilité vise deux choses : un but technique pour un concepteur et un but de gestion de projet. Pour le concepteur du logiciel cest un indicateur qui permet, dés les premières phases de la conception, didentifier les systèmes qui peuvent devenir difficiles à tester. Pour le chef de projet, cette mesure permet de trouver un compromis en terme de coûts parmi différentes solutions fondées sur diverses conceptions et techniques de test.
Quel que soit le facteur de qualité observé, voici deux exemples dinformations que le concepteur peut espérer obtenir :
localisation des points délicats dans la conception où le facteur peut avoir une valeur faible, et où larchitecture doit être améliorée
détection des changements inadaptés (raffinements, évolutions) qui peuvent mener à une variation non désirée de ce facteur
Lobjectif du test de logiciel nest pas seulement de couvrir et exécuter chaque partie du logiciel mais aussi de révéler des fautes cachées. De ce point de vue, les notions de contrôlabilité et dobservabilité dun composant logiciel, introduites par Freedman ADDIN EN.CITE Freedman19911400Freedman, R.S.1991Testability of Software ComponentsIEEE Transactions on Software Enginnering176553 - 564JuneZ:\biblio-bb\Testabilité\TestabilitySoftComp.pdf[Freedman''91], sont complémentaires du coût de test. Par exemple, la contrôlabilité est relative au rapport entre la taille du domaine des données de sortie et celle du domaine des données en entrée du logiciel. Cependant, son approche ne prend pas en compte la difficulté quil y a à exécuter un composant et à propager lerreur jusquà la sortie du logiciel. Cette limite a été analysée en détails par Voas ADDIN EN.CITE Voas19921420Voas, J. M.1992PIE : A Dynamic Failure-Based TechniqueIEEE Transactions on Software Enginnering188717 - 727AugustVoas1995110Voas, J. M.Miller, K.1995Software Testability: The New VerificationIEEE Software12317 - 28May[Voas''92b; Voas''95] au niveau du code du composant. Plusieurs mesures ont été proposées pour évaluer la perte dinformation dés la conception, telles que le Domain/Range Ratio de Voas ADDIN EN.CITE Voas19931410Voas, J. M.Miller, K.1993Semantic Metrics for Software TestabilityJournal of Systems and Software203207 - 216March[Voas''93] pour les programmes impératifs ou la mesure de contrôlabilité/observabilité pour les architectures flots de données ADDIN EN.CITE Le Traon19971363Le Traon, YvesRobach, Chantal1997Testability Measurements for Data Flow DesignsInternational Software Metrics Symposium (Metrics'97)Albuquerque, NM, USA91 - 98NovemberZ:\biblio-bb\Testabilité\TestabilityDataFlowDesign.pdfLe Traon20001143Le Traon, YvesOuabdessalam, FaridRobach, Chantal2000Analyzing Testability on Data Flow DesignsISSRE'00 (Int. Symposium on Software Reliability Engineering)San Jose, CA, USA162 - 173October[Le Traon''97; Le Traon''00b]. Weide et al. ADDIN EN.CITE Weide19961433Weide, B. W. Edwards, S. H. Heym, W. D. Long, T. J. Ogden, W. F.1996Characterizing Observability and Controllability of Software Components4th International Conference on Software ReuseOrlando, USA62 - 71April[Weide''96] ont défini lobservabilité et la contrôlabilité des types de données abstraits pour une meilleure réutilisation des composants logiciels. Quant à la mesure de la contrôlabilité et lobservabilité dans les programmes OO, aucune approche satisfaisante na été proposée.
En ce qui concerne létude de testabilité présentée au chapitre REF _Ref37421810 \r \h 5 de cette thèse, nous nous concentrons sur la mesure du coût global de test.
Coût global de test. Ce facteur évalue le coût nécessaire à la vérification dun critère de test donné. Cette mesure est relative à la taille de lensemble de cas de test, à la difficulté de trouver des données de test pour vérifier le critère ainsi quà la difficulté dévaluer la valeur doracle.
A propos des mesures du coût global de test, Bieman et Schultz ADDIN EN.CITE Bieman19891443Bieman, J.M.Schultz, J.1989Estimating the Number of Test Cases Required to Satisfy the all-du-paths Testing CriterionSoftware Testing Analysis and Verification Sympoium179 - 186December[Bieman''89] ont compté combien de cas de test sont nécessaires pour vérifier le critère de couverture de tous les chemins définition-utilisation ADDIN EN.CITE Rapps1985520Rapps, S. Weyuker, E. J.1985Selecting Software Test Data Using Data Flow InformationIEEE Transactions on Software Enginnering114367 - 375April[Rapps''85]. Il existe de nombreuses mesures pour les architectures OO (e.g. ADDIN EN.CITE Chidamber1994560Chidamber, S.R.Kemerer, C.F.1994A Metrics Suite for Object Oriented DesignIEEE Transactions on Software Enginnering206476 - 493JuneShepperd19989316Shepperd, Martin1998Object-Oriented Metrics: an Annotated Bibliographyhttp://dec.bournemouth.ac.uk/ESERG/bibliography.html[Chidamber''94; Shepperd''98]), mais la testabilité nest jamais mesurée directement. Cependant, elle peut être évaluée à partir de la mesure de couplage sous lhypothèse quun fort couplage dans une architecture OO entraîne une diminution de la testabilité. Le couplage mesure la force de la relation entre deux modules, i.e. des classes dans le cas des modèles orientés objet. Un grand nombre de mesures de couplage ont été proposées, chacune correspondant à un type de relations entre les classes. Dans ADDIN EN.CITE Briand1999550Briand, LionelDaly, J.W.Wüst, J.K.1999A Unified Framework for Coupling Measurement in Object-Oriented SystemsIEEE Transactions on Software Enginnering25191 - 121January/February[Briand''99], les auteurs proposent un classement des mesures de couplage pour des éléments de conception UML précis. La mesure de couplage entre objets (CBO) ADDIN EN.CITE Shyam1994940Shyam, S. R. Kemerer, C. F.1994A Metrics Suite for Object Oriented DesignIEEE Transactions on Software Enginnering206476 - 493JuneBriand1999550Briand, LionelDaly, J.W.Wüst, J.K.1999A Unified Framework for Coupling Measurement in Object-Oriented SystemsIEEE Transactions on Software Enginnering25191 - 121January/February[Shyam''94; Briand''99] correspond à un ensemble de classes qui emploient chacune l'autre. Dans le diagramme de classes UML, on dit qu'une classe a emploie une autre classe b s'il existe une association ou une dépendance entre ces classes. La mesure CBO est discutée en termes de testabilité dans ADDIN EN.CITE Binder1994490Binder, R. V.1994Design for Testability in Object-Oriented systemsCommunications of the ACM37987 - 101September[Binder''94], et des critères de test pour ce type de relations parmi les classes sont proposés dans ADDIN EN.CITE Alexander200023Alexander, R. T.Offutt, Jeff2000Criteria for Testing Polymorphic RelationshipsISSRE'00 (Int. Symposium on Software Reliability Engineering)San Jose, CA, USA15 - 23OctoberD:\Travail\biblio-bb\TestOO\CriteriaForPolymorph.pdf[Alexander''00]. Ces travaux se concentrent sur chaque chemin indépendamment et visent le comptage/couverture.
Dans le chapitre REF _Ref35170259 \r \h 5, nous nous concentrons sur des chemins particuliers dans le diagramme de classes, ainsi quà la complexité des interactions décrites par ces chemins (due au polymorphisme et à la liaison dynamique). À notre connaissance, la contribution précise à la testabilité de chaque dépendance participant au couplage n'a jamais été étudiée, particulièrement dans le cas de logiciels conçus en utilisant UML. Le but de lanalyse de testabilité proposée est donc plus d'indiquer les rôles des liens participants au couplage que de limiter le nombre de tels liens.
Conclusion
Le paradigme objets, ainsi que les constructions spécifiques aux divers langages orientés objets ont entraîné la nécessité dune adaptation de certains aspects du test de logiciel. Un grand nombre de travaux sur les test de programmes objets se fondent aujourdhui sur le langage UML comme langage de description. Au cours de cette thèse, nous nutilisons pas de propriétés particulières des diagrammes UML pour le test, mais utilisons la notation pour illustrer nos différents travaux.
Nous nous intéressons à trois aspects particuliers du tests pour la programmation objets. Tout dabord, nous étudions la génération automatique de cas de test efficaces (section REF _Ref38794395 \r \h 2.4) pour le test unitaire de classe ou de composant. Ensuite, nous nous intéressons au lien entre les assertions et le test de systèmes OO (section REF _Ref38794451 \r \h 2.5). Pour cela, nous mesurons limpact de la conception par contrats, comme une méthode de placements dassertions dans une architecture OO. Cet impact est évalué sur deux facteurs de qualité dun système : la robustesse et la diagnosabilité. Ces deux facteurs évaluent la capacité de détection et de localisation derreurs respectivement, et sont donc directement liés à lefficacité du test. Enfin, nous étudions la complexité des interactions entre composants, et proposons une mesure de la testabilité (section REF _Ref38794486 \r \h 2.6) dun assemblage de composants.
Génération automatique de cas de test pour un composant
Le test de logiciel est un moyen efficace pour estimer la confiance quil est possible davoir dans un composant logiciel. Dans le cadre de la méthode des composants autotestables, la mesure de confiance est fondée sur lévaluation de la cohérence entre limplantation, la spécification (embarquée dans le composant sous forme de contrats exécutables) et les cas de test. Cette cohérence étant évaluée grâce au test du composant, la qualité des cas de test est alors le facteur principal pour évaluer la confiance. Comme nous lavons vu section REF _Ref38684614 \r \h 2.3.3, nous utilisons lanalyse de mutation pour qualifier un ensemble de cas de test, et ce chapitre a pour but létude dalgorithmes évolutionnistes pour loptimisation automatique du score de mutation densembles de cas de test.
Alors que la génération dun ensemble de cas de test initiaux est simple, améliorer la qualité de cet ensemble demande un effort beaucoup plus grand. En effet, notre expérience montre que les cas de test fournis par le testeur détectent souvent entre 50 et 70% des mutants ADDIN EN.CITE Deveaux1999353Deveaux, DanielJézéquel, Jean-MarcLe Traon, Yves1999Self-testable Components: from Pragmatic Tests to a Design-for-Testability MethodologyTOOLS'99 (Technology of Object Oriented Languages and Systems)Nancy, France96 - 107June[Deveaux''99a], ce qui correspond à peu près au test des cas dutilisation normaux du programme sous test. Par contre, améliorer ce score pour couvrir plus de 90% des mutants est très coûteux tant en effort de génération quen temps. Le travail décrit dans ce chapitre se concentre sur lautomatisation de cette phase damélioration dun ensemble de cas de test initiaux.
Le problème de lamélioration automatique de cas de test est un problème non linéaire, qui peut facilement se ramener à un problème doptimisation de fonction (on veut trouver un ensemble de cas de test qui maximise le score de mutation pour un programme donné). Pour cela, nous appliquons un algorithme semi-aléatoire particulièrement adapté à lexploration de grands ensembles de solutions, lalgorithme génétique. Il existe une analogie forte entre le phénomène de sélection naturelle (duquel sinspirent les algorithmes génétiques) et la génération de cas de test à partir dun ensemble initial. Le problème doptimisation de cet ensemble est modélisé avec de lanalogie suivante : un cas de test est considéré comme un prédateur et un mutant comme une proie. A partir de lensemble de test fourni par le testeur, le but de la sélection est alors de détecter les meilleurs prédateurs (cas de test), i.e., ceux capables de tuer le plus de mutants, et de les améliorer dune génération à lautre. Nous présentons une modélisation de ce problème de test pour appliquer un algorithme génétique, ainsi que les résultats sur deux études de cas : une pour la génération de cas de test unitaire pour des classes Eiffel, et lautre pour des cas de test système (un parseur pour le langage C# développé sur la plate-forme Microsoft.NET ADDIN EN.CITE MSDN20024716MSDN2002C# Introduction and Overview2002marchhttp://msdn.microsoft.com/vstudio/techinfo/articles/upgrade/Csharpintro.aspMSDN20026016MSDN2002.NET homepage2002http://msdn.microsoft.com/library/default.asp?url=/nhp/Default.asp?contentid=28000519[MSDN''02a; MSDN''02b]).
Alors que les résultats de ces expériences savérèrent moins bons que ce que nous attendions, des collègues biologistes, du laboratoire ECOBIO de luniversité de Rennes 1, nous suggérèrent dadapter notre modèle pour nous concentrer sur ladaptation au niveau bactériologique (des bactéries qui sadaptent à un milieu déterminé) plutôt quau niveau animal (des lions qui tuent des zèbres). Une approche fondée sur une analogie avec le phénomène dadaptation bactériologique ADDIN EN.CITE Rosenzweig1995341Rosenzweig, Michael L.1995Species Diversity In Space and TimeCambridge University Press436[Rosenzweig''95] semble plus appropriée pour loptimisation automatique de cas de test, en modifiant les algorithmes génétiques par lajout dune fonction de mémorisation des meilleurs cas de test, et la suppression de la notion dindividu (animal) et de lopération de croisement. Nous décrivons ce nouveau modèle, le modèle bactériologique et son comportement sur les études de cas précédentes. Nous détaillons aussi le paramétrage du modèle puisque cest un des aspects essentiels pour lefficacité de lapproche. Enfin, nous présentons une approche intermédiaire entre un algorithme génétique « pur » et un algorithme bactériologique « pur », issue des différentes expériences exécutées pour le calibrage.
Avant de présenter nos travaux sur létude dalgorithmes évolutionnistes pour loptimisation automatique de cas de test, nous détaillons lanalyse de mutation. Cette technique, qui a été brièvement abordée dans le chapitre précédent, a en effet été fortement adaptée, et utilisée de façon intensive pour évaluer la qualité dun ensemble de cas de test pour un composant.
Adaptation de lanalyse de mutation pour la qualification de composants
Lanalyse de mutation est une technique proposée par DeMillo ADDIN EN.CITE DeMillo1978270DeMillo, R.Lipton, R.Sayward, F.1978Hints on Test Data Selection : Help For The Practicing ProgrammerIEEE Computer11434 - 41AprilZ:\biblio-bb\Mutation\HintsTestDataSelectino.pdf[DeMillo''78] pour valider et améliorer un ensemble de données de test. Le processus consiste à créer un ensemble de versions erronées du programme à tester appelées des mutants, puis à trouver un ensemble de données de test capable de détecter ces erreurs. Un vocabulaire particulier est associé à cette approche : si un cas de test peut détecter un mutant, on dit quil tue le mutant, sinon le mutant est vivant. Un mutant est une copie exacte du programme sous test dans lequel une seule erreur simple a été injectée. En pratique, lensemble des mutants est créé en soumettant le programme à des opérateurs de mutation qui correspondent à différents types derreur (changement de signe des constantes, changement de sens dune inégalité
).
Après une présentation du processus global pour lanalyse de mutation, nous détaillons plusieurs aspects de cette technique que nous avons adaptée pour fiabiliser des composants logiciels. En particulier, nous proposons une solution originale pour loracle, nous présentons des outils pour lanalyse de mutation développés au sein de léquipe. Enfin, nous présentons larchitecture de notre approche qui permet dexécuter une analyse de mutation pour une classe unitaire ou pour un système composé de plusieurs classe.
Le processus global
Figure SEQ Figure \* ARABIC 9 Exemples de mutant
Une analyse de mutation prend en entrée un programme sous test, et un ensemble de cas de test pour ce programme. Les mutants pour le programme sous test peuvent ensuite être générés automatiquement à partir de la définition dun ensemble dopérateurs de mutation. La REF _Ref45003726 \h Figure 9 illustre deux mutants qui peuvent être générés pour une méthode addMoney(). Le mutant 1 a été généré en ajoutant 1 à la variable m, le mutant 2 a été généré en supprimant une instruction.
Tous les cas de test associés au programme sont ensuite exécutés avec chacun des mutants. On obtient alors la signature de chaque cas de test (lensemble des mutants tués par un cas de test), et à partir de cette signature, le score de mutation de chaque cas de test (la proportion de mutants tués par un cas de test). Le processus est illustré REF _Ref32896383 \h Figure 10.
Signature dun cas de test. La signature dun cas de test, Sign(t), est lensemble des mutants non équivalents tués par ce cas de test:
Sign(t) = {mutants tués par t}
Le score de mutation dun cas de test est calculé à partir de sa signature.
Score de mutation. Soit m le nombre total de mutants non équivalents dun programme, le score de mutation de t est donné par la formule suivante:
EMBED Equation.3
On peut aussi calculer la signature et le score de mutation dun ensemble de tests.
Valeurs globales dun ensemble de tests. La signature globale dun ensemble T = {t1
tn} de cas de test est lunion des signatures de tous les cas de test de lensemble:
EMBED Equation.3
et le score global est: EMBED Equation.3
Le score de mutation associé à un ensemble de cas de test correspond à lestimateur de la qualité de cet ensemble de cas de test.
Figure SEQ Figure \* ARABIC 10 Processus global de génération de tests par mutation
Loracle pour lanalyse de mutation
Lors de lexécution des cas de test sur lensemble des mutants, il faut une fonction doracle qui évalue si le cas de test tue le mutant. Lorsque nous exécutons des analyses de mutation dans la suite de cette thèse, nous utilisons deux types doracle. La première fonction doracle consiste à repérer une différence de comportement entre le mutant et le programme original. Elle correspond à la fonction la plus généralement utilisée pour la mutation. Lidée consiste à dire que, puisque les cas de test sont censés avoir détecté les erreurs du programme, et puisquil ny a aucun moyen dassurer la qualité du programme initial, on considère ce dernier comme correct et on teste les cas de test en les mettant à lépreuve sur des versions volontairement erronées du programme original. La seconde fonction utilise les assertions embarquées dans le code, si elles existent. Dans les cas des expériences décrites par la suite (chapitre REF _Ref37421743 \r \h 4), nous utilisons les contrats comme valeur doracle. Ceci constitue une contribution originale de cette thèse.
Oracle par différence de comportement. Soit Outo lensemble des sorties obtenues en exécutant un cas de test sur le programme original, Outm lensemble des sorties obtenues en exécutant un cas de test sur un mutant. Si Outo ( Outm, alors le mutant est tué par le cas de test. Cest ce quon appelle la fonction doracle par différence de comportement.
Oracle fondé sur les assertions. Si au cours de lexécution dun cas de test sur un mutant, une assertion est violée, on considère que le cas de test a détecté le mutant.
La fonction doracle par différence de comportement est la fonction généralement utilisée pour lanalyse de mutation. Dans ce cas, la génération de test se fait de manière itérative. Le testeur exécute une première analyse de mutation en faisant lhypothèse que le programme original est correct. Il obtient alors un ensemble de cas de test, puis il exécute ces cas de test sur le programme original et des erreurs peuvent alors être détectées. Lorsquelles sont corrigées, un seconde analyse de mutation est exécutée puisque le programme original a changé, et donc la fonction doracle également. De nouveaux cas de test sont ainsi obtenus.
Dans cette thèse, nous utilisons deux notions de « sortie » pour comparer les comportements. La première, « classique », est fondée sur la trace dexécution du programme. La seconde, spécifique à lobjet, est fondée sur la différence entre les objets du programme initial et les objets du programme mutant. Dans ce cas, la différence de comportement est vérifiée en comparant les valeurs des attributs des objets, après lexécution dun cas de test. Cette seconde manière de fabriquer loracle est une contribution technique secondaire de ce manuscrit. Cela permet de saffranchir des traces explicites, et permet notamment davoir un oracle efficace, même dans le cas de programmes ayant peu de sorties explicites. On peut noter que lobservation de la différence de comportement est considérée comme un « oracle » par abus de langage, puisquen pratique on ne sait pas si le cas de test a détecté une faute non intentionnelle dans le programme initial.
Nous verrons par la suite comment utiliser loracle fondé sur les assertions. Il est employé dans deux contextes : lamélioration des contrats embarqués dans le composant (sectoin REF _Ref38539149 \r \h 4.2.5a), et lévaluation de lapport des assertions pour la robustesse et la diagnosabilité dun composant logiciel (section REF _Ref38539150 \r \h 4.2.5b).
Enfin, nous utilisons un troisième type doracle, en associant des assertions aux cas de test (comme le requiert le format JUnit par exemple). Cest la cas le plus classique pour lexpression de loracle.
A la fin dune analyse de mutation, un diagnostic doit être fait pour savoir pourquoi certains mutants nont pas été tués. Trois raisons peuvent empêcher la détection dun mutant : aucune donnée de test ne permet dexhiber une défaillance lors de lexécution de linstruction erronée, les assertions dans le code sont trop « faibles » pour détecter lerreur, ou le mutant est équivalent au programme original. Dans le premier cas, soit aucun cas de test ne permet de couvrir linstruction erronée, soit la donnée de test na pas permis de produire lerreur en exécutant linstruction mutée. Dans le cas où on utilise un oracle fondé sur les assertions, un mutant peut ne pas être tué à cause dune incomplétude des assertions. La section suivante détaille le problème des mutants équivalents.
Remarque : les deux premières raisons ne sont pas exclusives lune de lautre : même si un cas de test exécute linstruction erronée et détecte ainsi lerreur, les assertions peuvent être trop faibles pour la détecter.
Mutants équivalents
Parmi les mutants générés, certains sont équivalents au programme initial.
Mutant équivalent. Soit Di le domaine des valeurs dentrée du programme à tester, on dit quun mutant est équivalent au programme initial sil nexiste aucune séquence de valeurs de Di qui permette de distinguer le programme mutant du programme initial. Soit Outo(x1,
xn) les sorties du programme original pour lensemble des entrées (x1,
xn) et Outm(x1,
xn) les sorties pour un mutant, le mutant est dit équivalent si :((((x1,
xn)(Di) / Outm(x1,
xn) ( Outo(x1,
xn)
Sur lexemple de la REF _Ref32896350 \h Figure 11, la variable I a été remplacée par MinVal dans la seconde instruction du programme mutant. Or, linstruction précédente affecte la valeur I à MinVal. A ce point du programme MinVal et I ont donc toujours la même valeur. Le programme mutant est équivalent au programme initial.
Figure SEQ Figure \* ARABIC 11 Exemple de mutant équivalent
Il est important de détecter les mutants équivalents et de les éliminer de lensemble de mutants à tuer. En effet, par définition, il est impossible décrire un cas de test qui distingue un mutant équivalent du programme initial, et les mutants équivalents ne peuvent donc pas être tués. Ils sont en fait corrects. Dans ADDIN EN.CITE Offutt1997960Offutt, A. J.Pan, J.1997Automatically Detecting Equivalent Mutants and Infeasible PathsThe Journal of Software Testing, Verification and Reliability73165 - 192September[Offutt''97], les auteurs proposent une technique pour détecter automatiquement certains types de mutants équivalents. Lidée consiste à ramener le problème de la génération de test à un problème de résolution de contrainte. Pour tuer un mutant, il faut générer une donnée de test qui satisfasse une contrainte particulière. Si la contrainte ne peut être satisfaite alors le mutant est équivalent.
Cependant, cette détection se fait généralement à la main, ce qui augmente le coût de lanalyse de mutation. De manière pratique, on écrit un premier ensemble de cas de test qui tue un sous-ensemble des mutants. On cherche ensuite les mutants équivalents parmi ceux encore vivants, ce qui évite dobserver tous les mutants générés.
Opérateurs de mutation utilisés pour la qualification des composants
Dans notre étude, nous appliquons lanalyse de mutation à la qualification de tests pour des programmes objet. Certains opérateurs sont donc liés à ce type de programme, dautres sont des opérateurs couramment utilisés par les différents outils de mutation. Tous les opérateurs sont donnés dans la REF _Ref38785950 \h Tableau 1. Au moment de notre étude, les opérateurs proposés étaient originaux. Entre-temps, Offutt ADDIN EN.CITE Ma20021253Ma, Yu-SeungKwon, Yong-RaeOffutt, A. J.2002Inter-Class Mutation OperatorsISSRE'02 (Int. Symposium on Software Reliability Engineering)Annapolis, MD, USA352 - 363NovemberZ:\biblio-bb\Mutation\InterClassMutation.pdf[Ma''02] et Kim ADDIN EN.CITE Kim2001590Kim, Sun-Woo Clark, J.A. McDermid, J.A.2001Investigating the effectiveness of object-oriented testing strategies using the mutation methodSoftware Testing, Verification and Reliability114207 - 225DecemberZ:\biblio-bb\Mutation\EffectiveOOMutation.pdf[Kim''01] ont proposé de nouveaux opérateurs pour les programmes OO, quil nous a, malheureusement, été impossible dutiliser.
TypeDescriptionEHFException Handling FaultAORArithmetic Operator ReplacementLORLogical Operator ReplacementRORRelational Operator ReplacementNORNo Operation ReplacementVCPVariable and Constant PerturbationMCRMethods Call ReplacementRFIReferencing Fault InsertionTableau SEQ Tableau \* ARABIC 1 Opérateurs de mutation pour les programmes objet
EHF : Insertion dune instruction dexception qui est systématiquement déclenchée lorsquelle est exécutée.
AOR : Remplace les occurrences de + par - et vice et versa.
Opérateur arithmétiqueRemplacé par+-, *-+, / (ou div)*/ (ou div), +/*, -div-, modmod-, divLOR : Chaque occurrence dun opérateur logique (et, ou, non-et, non-ou, ou exclusif) est remplacée par chacun des autres opérateurs. De plus, lexpression est remplacée par VRAI et FAUX.
Opérateur relationnelRemplacé par((< et >(=( et (ROR : Chaque occurrence dun opérateur relationnel (, (, (, =, () est remplacée par chacun des autres opérateurs. Pour éviter davoir un trop grand nombre de mutants, on applique les règles suivantes présentées dans le tableau ci-dessus.
NOR : Supprime une instruction.
VCP : Les valeurs des constantes et des variables sont légèrement modifiées pour effectuer une analyse de sensibilité (sensitivity analysis) analogue à ce que propose Voas dans ADDIN EN.CITE Voas1992260Voas, Jeffrey M.Miller, K.1992The Revealing Power of a Test CaseSoftware Testing, Verification and Reliability2125 - 42May[Voas''92a]. Chaque constante ou variable de type arithmétique est incrémentée ou décrémentée de un. Chaque booléen est remplacé par son complément.
MCP : Les appels de méthode sont remplacés par un appel à une autre méthode qui a la même signature.
RFI : Force à void la référence à un objet après sa création. Supprime une instruction de clonage ou de copie. Introduit une instruction de clonage pour chaque affectation dune référence.
Les opérateurs de mutation AOR, LOR, ROR et NOR sont des opérateurs de mutation traditionnels étudiés dans ADDIN EN.CITE Offutt1996250Offutt, A. J.Pan, J.Tewary, K. Zhang, T.1996An experimental evaluation of data flow and mutation testingSoftware Practice and Experience262FebruaryDeMillo1991380DeMillo, R.Offutt, A.J.1991Constraint-Based Automatic Test Data GenerationIEEE Transactions on Software Enginnering179900 - 910SeptemberZ:\biblio-bb\TestGeneration\ConstraintBased.pdf[DeMillo''91; Offutt''96a]. Nous avons introduits les autres opérateurs pour le test dans le domaine des programmes objet. Lopérateur RFI injecte des fautes de référence à des objets (fautes daliasing) qui sont spécifiques au domaine de la programmation objet :
la référence à un objet est forcée à void (en Eiffel) ou null (en Java et C#).
les instructions de duplication dobjets sont supprimées
chaque affectation dune référence à un objet est précédée de la duplication de cet objet
Les fautes introduites par lopérateur RFI sont plus difficiles à détecter que celles introduites par les autres opérateurs.
Outils
Trois outils pour lanalyse de mutation ont été développés au sein de léquipe Triskell, chacun dentre eux étant destiné au test de programmes dans un langage particulier. Les trois langages étudiés sont des langages orientés objet : Eiffel, Java et C#. Les principes de fonctionnement de ces outils sont similaires : ils prennent tous les trois un programme sous test et un ensemble de cas de test en entrée. Ensuite loutil génère les mutants automatiquement. Un mode interactif permet à lutilisateur de paramétrer la génération : uniquement les mutants pour un ou plusieurs opérateurs particuliers ou un nombre déterminé de mutants au hasard parmi tous les mutants possibles ou encore, tous les mutants possibles. Une fois que lensemble de mutants sur lequel on veut lancer lanalyse de mutation est déterminé, loutil exécute chaque cas de test sur chaque mutant et calcule la signature des cas de test ainsi que le score de mutation global.
L outil pour Eiffel est baptisé ¼Slayer ADDIN EN.CITE Deveaux19992003Deveaux, D.Jézéquel, Jean-MarcLe Traon, Yves1999Self-testable components: from pragmatic tests to design-for-testability methodology.Technology of Object Oriented Languages and Systems (TOOLS'99)Nancy, France96-107June[Deveaux''99b] et permet dexécuter une analyse de mutation pour une classe Eiffel. Il implante tous les opérateurs décrits dans la section REF _Ref37932493 \r \h 3.1.4. Les cas de test en entrée de ¼Slayer doivent être regroupés dans une seule classe. Lors de l analyse de mutation, toutes les méthodes de cette classe de test sont exécutées avec chaque mutant.
L outil pour Java est baptisé JMutator ADDIN EN.CITE Deveaux20012013Deveaux, D.Le Traon, Yves2001XML to Manage Source Code Engineering in Object-Oriented Development: an Example.XSE01 workshop at ICSE'2001Toronto, Canada28 - 31May[Deveaux''01] et permet dexécuter une analyse de mutation sur une ou plusieurs classes Java. Il implante tous les opérateurs décrits dans la section REF _Ref37932493 \r \h 3.1.4. Cet outil sappuie sur deux outils pour Java : JavaCC (Javas Compiler Compiler) ADDIN EN.CITE SUN200020216SUN2000JavaCChttp://www.webgain.com/products/java_cc/[SUN''00] et JUnit ADDIN EN.CITE Beck20014816Beck, KentGamma, E.2001JUnit2002http://www.junit.org/index.htm[Beck''01]. Le parseur de JMutator, nécessaire à la génération des mutants, a été généré automatiquement avec JavaCC. Les cas de test en entrée de JMutator doivent être écrits au format JUnit, ce qui permet dutiliser les mécanismes propres au framework JUnit pour exécuter les cas de test et récupérer la cause de lerreur.
Enfin, loutil pour les programmes C#, baptisé NMutator, a pour particularité dêtre dédié à lanalyse de mutation sur des systèmes orientés objet. Comme nous le verrons au chapitre REF _Ref37935225 \r \h 3, ceci implique de ne générer que certains types de mutants, NMutator nimplante donc que les opérateurs NOR et AOR. Le parseur pour cet outil est inspiré du parseur de Mono, un projet de compilateur pour C# pour linux ADDIN EN.CITE de Icaza200120316de Icaza, Miguel2001mono : a C# compiler for Linux.http://www.go-mono.com/index.html[de Icaza''01]. Par ailleurs, NMutator permet la parallélisation de lexécution des cas de test sur différents processus.
Les outils JMutator et NMutator sont disponibles en ligne à ladresse suivante : HYPERLINK "http://franck.fleurey.free/index.htm" http://franck.fleurey.free/index.htm.
Analyse de mutation pour une classe ou un système
Dans la suite de ce chapitre, nous proposons doptimiser automatiquement un ensemble de cas de test pour une classe ou un système composé dun ensemble de classes, et nous utilisons lanalyse de mutation pour qualifier ces cas de test. Cette technique est généralement appliquée au niveau unitaire, et un mutant est alors une classe avec une erreur. Dans le cas du test système, un mutant est une copie du système sous test dans laquelle on a injecté une erreur.
Au moment du test système, on suppose que la phase de test unitaire a permis de valider les classes séparément, et on se concentre sur le test des interactions entre classes. Les deux étapes de test ne cherchant pas à détecter le même type derreur, lanalyse de mutation doit être adaptée aux deux situations. De plus, des considérations techniques doivent être prises en compte pour appliquer la mutation à un système. Alors quil est raisonnable dinjecter de nombreuses erreurs dans une classe, ce nest pas raisonnable au niveau système. En effet, les temps de compilation et dexécution pour appliquer tous les cas de test sur un système mutant sont beaucoup plus important que pour une classe isolée. De plus, sil est possible de détecter les mutants équivalents pour une classe dans la plupart des cas (le plus souvent à la main), il est beaucoup plus difficile de détecter un système équivalent.
Pour appliquer lanalyse de mutation à la génération de cas de test système, nous avons donc choisi un sous-ensemble dopérateurs pour générer moins de mutants. De plus, ces opérateurs ne doivent pas générer de mutants équivalents. Pour les études de cas présentées dans ce chapitre, nous avons choisi les opérateurs LOR (logical operator replacement) et NOR (No operation replacement) pour le test système. La REF _Ref38535248 \h Figure 12 présente larchitecture générique pour un générateur de mutant : dans le cas du test unitaire, le générateur utilise tous les opérateurs définis précédemment ( qui implantent linterface OpérateurUnitaire), alors que dans le cas du tes système, seuls les opérateurs LOR et NOR (qui implantent linterface OpérateurSystème) sont utilisés.
Une autre solution pour éviter de générer trop de mutants, que nous navons pas eu le temps dexplorer au cours de cette thèse, consiste à sélectionner les classes dans lesquelles des erreurs doivent être injectées en fonction dun certain critère. Plusieurs critères semblent possibles : uniquement les classes de contrôle, les classes les plus éloignées de lentrée du système
Figure SEQ Figure \* ARABIC 12 Architecture pour la génération de mutants
Optimisation automatique et analyse de mutation
Pour conclure cette section sur lanalyse de mutation, et introduire la problématique principale de ce chapitre, la REF _Ref38536628 \h \* MERGEFORMAT Figure 13 illustre le processus de mutation dans lequel la phase damélioration de cas de test est automatisée. Dans le cas où lensemble de cas de test nest pas assez efficace, lopérateur Optimiseur essaie de laméliorer automatiquement. Dans la suite de ce chapitre nous étudions deux algorithmes évolutionnistes pour implanter cet opérateur.
Figure SEQ Figure \* ARABIC 13 Amélioration automatique de cas de test pour lanalyse de mutation
Figure SEQ Figure \* ARABIC 14 Architecture globale pour la génération de test
La REF _Ref38537302 \h Figure 14 illustre larchitecture UML globale pour la génération automatique de test fondée sur lanalyse de mutation pour la qualification de cas de test. La classe Testeur est chargée de la connection entre le ComposantSousTest, le générateur de mutants représenté par la classe Mutateur, et le TestOptimiseur. Dans ce chapitre, nous nous intéressons à loptimisation de cas de test pour une Classe ou un Système. A un composant sous test, on associe un ensemble de cas de test. Dans les sections REF _Ref38538527 \r \h 3.2.3b et REF _Ref43967926 \r \h 3.2.3c, nous détaillons la modélisation dun CasTestSystème ou CasTestUnitaire.
Algorithme génétique pour loptimisation de cas de test
La confiance que lon peut avoir en un composant dépend de la fiabilité de ses tests et de ses contrats. Une étude récente ADDIN EN.CITE Deveaux1999353Deveaux, DanielJézéquel, Jean-MarcLe Traon, Yves1999Self-testable Components: from Pragmatic Tests to a Design-for-Testability MethodologyTOOLS'99 (Technology of Object Oriented Languages and Systems)Nancy, France96 - 107June[Deveaux''99a] a montré quil est facile, pour un testeur, décrire à la main des tests obtenant un score de mutation entre 50 et 70%, mais lobtention dun score plus élevé réclame un effort important. Le but de notre étude est donc dautomatiser loptimisation dun ensemble initial de tests.
Dans cette section, nous proposons une première méthode pour résoudre ce problème. Cette approche est fondée sur un algorithme génétique qui devra optimiser le score de mutation de lensemble initial de tests. Nous commençons par présenter globalement les algorithmes génétiques, puis la manière dont nous les avons appliqués à notre problème. Nous présentons ensuite les expériences que nous avons effectuées avec ce modèle, et les conclusions que nous en avons tirées.
Les algorithmes génétiques
Les algorithmes génétiques ADDIN EN.CITE Goldberg1989401Goldberg, D. E.1989Genetic Algorithms in Search, Optimization and Machine LearningAddison-Wesley[Goldberg''89] ont dabord été développés par John Holland ADDIN EN.CITE Holland1974691Holland, J. H.1974Adaptation in Natural and Artificial SystemsUniversity of Michigan Press[Holland''74] dont le but était dexpliquer les systèmes naturels et de concevoir des systèmes artificiels fondés sur ces mécanismes naturels. Les algorithmes génétiques sont donc des algorithmes doptimisation fondés sur les principes de la sélection naturelle. Dans la nature, les individus les mieux adaptés à leur environnement (qui sont capables déchapper aux prédateurs, de se protéger du froid
) se reproduisent, et grâce aux croisements et à la mutation, la génération suivante sera, normalement, encore mieux adaptée. Cest exactement ainsi que fonctionnent les algorithmes génétiques : à partir dun critère objectif ils permettent de sélectionner les meilleurs individus qui se reproduiront pour fournir la génération suivante.
Les algorithmes génétiques sont relativement simples à programmer, et sont particulièrement utiles lorsque lespace de recherche dune solution est très grand et quil existe des optima locaux. De plus, la recherche dun optimum se fait parmi un ensemble de solutions possibles et non pas en observant le comportement de solutions isolées. En effet, un algorithme génétique part dun ensemble initial de solutions, souvent créées aléatoirement, dans lequel il sélectionne celles qui sont les plus proches de loptimale. A partir de cet échantillon, il crée de nouvelles solutions par croisement et mutation. Comme les nouvelles solutions sont créées à partir des meilleures de la génération précédente, elles ont des chances dêtre meilleures que leurs ancêtres.
Pour appliquer un algorithme génétique à un problème particulier, il faut décomposer celui-ci en unités atomiques qui correspondent à des gènes pour le modèle. Des individus sont ensuite construits en une suite ordonnée de gènes. Les individus pour une modélisation particulière, ont tous la même taille (le même nombre de gènes). Un ensemble dindividus pour lalgorithme est appelé une population. Pour guider la sélection, il faut définir une fonction dutilité U qui, pour chaque individu dune population, rend une valeur U(x) qui correspond à la qualité de lindividu par rapport au problème que lon veut résoudre. Cette fonction est le critère à maximiser sur la population de départ. De plus, un algorithme génétique utilise trois opérations:
la reproduction a pour but de sélectionner les individus dune population qui vont participer à la génération suivante. Cette opération consiste en un tirage au sort pour lequel les individus sont pondérés par leur valeur dutilité. Ce tirage revient au lancement dune roulette où chaque individu aurait une part proportionnelle à sa valeur dutilité.
Sur lexemple de la REF _Ref20222181 \h Figure 15, on voit bien quen lançant cette roulette, les individus possédant la plus grande valeur dutilité ont plus de chances dêtre tirés, mais la probabilité de tirer un individu moins bon nest pas nulle: ils peuvent contenir quelques gènes bénéfiques à la population.
Figure SEQ Figure \* ARABIC 15 Fonctionnement de lopérateur de reproduction
le croisement consiste à tirer au hasard un nombre k positif inférieur à la taille n des individus. Ensuite, à partir de deux individus A et B deux autres individus sont créés, lun formé des k premiers gènes de A et de n-k derniers gènes de B, lautre formé des n-k derniers gènes de A et des k premiers gènes de B. Il existe dautres opérateurs de croisement, mais celui décrit ici est le plus couramment utilisé.
la mutation consiste à modifier la valeur dun ou plusieurs gènes dun individu.
Exemple : si un individu est une suite de gènes codés chacun sur un bit, la mutation dun gène consiste à lui donner la valeur 0 si sa valeur initiale était 1, ou 1 sinon.
Figure SEQ Figure \* ARABIC 16 Processus global pour un algorithme génétique
Habituellement, les opérateurs de reproduction et surtout de croisement sont si importants pour la convergence vers la meilleure solution (sélection parmi les meilleurs et croisement de ces individus), que lopérateur de mutation joue un rôle secondaire (le taux de mutation à une génération tourne typiquement autour de 2%). Le processus global de lalgorithme est décrit REF _Ref20222367 \h Figure 16 et le texte est détaillé dans la section suivant.
Texte de lalgorithme
Pour la description du texte de lalgorithme génétique, on suppose lexistence des fonctions select, rand(Entier, Entier) et rand(Population). Limplantation des fonctions Utilité et Mute dépend de lapplication particulière de lalgorithme génétique. Toutes ces fonctions sont décrites brièvement avant lalgorithme génétique.
// La fonction select prend une population et une séquence de réels en entrée. La séquence de réels doit être de la même taille que la population. La fonction rend un individu tiré au hasard dans la population, un individu i ayant une probabilité proba[i] dêtre tiré.
select(Population pop, Ens[Réel] proba) : Individu
//La fonction rand rend un entier au hasard entre x et y
rand(Entier x, y) : Entier
//La fonction rand rend un individu au hasard dans pop
rand(Population pop) : Individu
//La fonction Utilité rend la valeur dutilité dun individu
Utilité (Individu ind) : Réel
//La fonction Mute rend lindividu ind dans lequel un gène a été muté
Mute (Individu ind) : Individu
//Un algorithme génétique prend une population initiale, un objectif et un taux de mutation en entrée. Il explore lespace des solutions jusquà trouver un individu dont lutilité est supérieure ou égale à lobjectif
Algo_génétique (Population P, Réel obj, Réel tx_mutation) : Individu
local Population Prep, Pcrois, Pmut ;
début
tantque ( ! (( i ( [0..P.taille-1] | Utilité(P[i]) > obj)
faire
Prep ( ( ; Pcrois ( ( ; Pmut ( ( ;
Prep ( Reproduction(P) ;
Pcrois ( Croisement (Prep) ;
Pmut ( Mutation(Pcrois, tx_mutation) ;
P ( Pmut ;
fait
Result ( (P[i] | Utilité(P[i]) > obj ;
fin
Reproduction (Population pop) : Population
local Ens[Réel] proba ;
Réel u_cumulée ;
Population poptmp ;
début
u_cumulée ( EMBED Equation.3 ;
(i ( [O..pop.taille-1] proba[i] ( pop[i] * (Utilité(pop[i])/u_cumulée) ;
poptmp ( ( ;
pour i de 0 à pop.taille-1 faire
poptmp ( poptmp ( select(pop, proba);
fait
Result ( poptmp ;
fin
Croisement (Population pop) : Population
local Ens[Individu] croisement ;
Population poptmp ;
début
poptmp ( ( ;
pour i de 0 à pop.taille-1 faire
croisement ( Croise(pop [i], pop [i+1]) ;
poptmp ( croisement[0] ( croisement[1] ( poptmp ;
fait
Result ( poptmp ;
fin
Croise(Individu ind1, ind2) : Ens[Individu]
local Entier n ;
Individu ind3, ind4 ;
début
taille ( ind1.taille ;
n ( rand(0, taille-1) ;
ind3 ( ind1[O..n] ( ind2[n+1..taille-1];
ind4 ( ind2[0..n] ( ind1[n+1..taille-1];
Result ( ind3 ( ind4
fin
Mutation (Population pop, Réel tx_mutation) : Population
local Population poptmp ;
début
poptmp ( ( ;
pour i de 0 à (pop.taille * tx_mutation) - 1 faire
pop ( pop \ rand(pop) ;
poptmp ( Mute(ind) ( poptmp ;
fait
Result ( pop ( poptmp ;
fin
Le problème de loptimisation de cas de test
Cette section détaille la modélisation du problème de loptimisation automatique de cas de test pour appliquer un algorithme génétique. Nous présentons dabord un modèle générique pour loptimisation de nimporte quel type de donnée de test. Nous discutons ensuite des problèmes et adaptations du modèle spécifiques à la génération des cas de test unitaires ou système.
Modélisation générique des algorithmes génétiques pour le test
La REF _Ref20224830 \h Figure 17 présente une architecture générique pour loptimisation automatique de cas de test à laide dun algorithme génétique. Cette architecture est générique en ce sens quelle peut être spécialisée pour une phase de test donnée. Ici, nous proposons deux axes de spécialisation que sont le test unitaire de classe et le test système (ou dun paquetage de classes). Un algorithme génétique est une technique pour optimiser un ensemble de cas de test, la classe Génétique hérite donc de TestOptimiseur. Le problème est décomposé en une population qui est composée dindividus eux-mêmes constitués dun ensemble ordonné de gènes (cf. section REF _Ref20281875 \r \h 3.2.1). Les tailles de la population et des individus sont des constantes fixées pour une application particulière dun algorithme génétique.
Figure SEQ Figure \* ARABIC 17 Une architecture générique pour l'optimisation de cas de test à l'aide d'un algorithme génétique
La modélisation dun gène est particulière au problème de loptimisation de cas de test : il correspond à un cas de test (CasTest implémente linterface Gène). Il apparaît sur le diagramme que la classe CasTest se spécialise en CasTestUnitaire et CasTestSystème. Nous détaillons les particularités de modélisation pour les cas de test unitaires ou systèmes dans les sections suivantes.
Cette modélisation du gène implique quun individu est un ensemble de cas de test. Un individu correspond donc bien à ce que lon veut optimiser ici : un ensemble de cas de test pour un système ou pour une unité (une classe dans un cadre orienté objet).
Un autre paramètre doit être modélisé pour une application particulière de lalgorithme génétique : la fonction dutilité. Nous avons choisi le score de mutation comme mesure de lutilité dun individu. La classe Génétique encapsule donc lensemble de mutants sur lequel le score de mutation sera calculé.
Enfin, il faut définir les opérateurs pour lalgorithme. Les opérations de reproduction et de croisement peuvent être définies de manière générique, puisque la modélisation des individus ne dépend pas dun type particulier de cas de test. Par contre, comme lopération de mutation seffectue sur les gènes, et que la forme de ceux-ci dépend du type de cas de test généré, cette opération sera détaillée dans les sections spécifiques à chaque type de cas de test. Cette distinction entre les opérations apparaît sur le diagramme de la REF _Ref20224830 \h Figure 17 : les opérations de reproduction et de croisement apparaissent directement dans la classe Génétique, alors que lopération de mutation est réifiée en MutationUnitaire et MutationSystème.
Reproduction : les individus sont reproduits en prenant leur score de mutation comme valeur dutilité.
Croisement : soit m la taille des individus dune population, et i un entier tiré au hasard entre 1 et m-1. Alors, à partir de deux individus ind1 et ind2, deux nouveaux individus sont créés, le premier avec les i premiers gènes de ind1 et les m-i derniers gènes de ind2, et lautre avec les i premier gènes de ind2 et les m-i derniers de ind1. Cet opération est illustrée sur la REF _Ref20285374 \h Figure 18.
Figure SEQ Figure \* ARABIC 18 Opérateur de croisement
Les sections suivantes détaillent les aspects de la modélisation dépendant du type de cas de test à générer : le modèle de gène et lopérateur de mutation. Dans les deux cas, un gène doit être un cas de test pour rester cohérent avec le modèle de la REF _Ref20224830 \h Figure 17. Par ailleurs, le modèle pour un gène doit être correct pour les autres opérations, notamment le croisement. En effet, au cours de cette opération, il faut pouvoir changer lordre des gènes à lintérieur dun individu, et que celui-ci reste une entrée valide pour le programme sous test.
Spécialisation pour le test unitaire
Nous considérons que la classe est lunité pour le test unitaire de système orienté objet. Un cas de test pour une classe unitaire est alors une méthode qui crée une ou plusieurs instances de la classe sous test, et appelle des méthodes sur ces objets. Les cas de test peuvent être ensuite regroupés dans une classe de test. Cette structuration des cas de test unitaire est largement adoptée depuis lémergence des frameworks de test XUnit disponibles pour de nombreux langages ( HYPERLINK "http://www.junit.org/index.htm" http://www.junit.org/index.htm).
Pour appliquer un algorithme génétique pour loptimisation automatique de cas de test unitaires, nous avons modélisé le gène par une méthode qui correspond à un cas de test unitaire dans une classe de test. Deux parties peuvent être clairement identifiées dans le corps de cette méthode : linitialisation qui consiste à créer des instances de la classe sous test (correspond au setup dans les frameworks XUnit), et lappel de méthodes sur ces objets. La REF _Ref37316055 \h Figure 19 illustre un exemple de gène pour la génération automatique de cas de test unitaire pour une classe Eiffel. La méthode test_set de la classe test_date est un gène, et les parties initialisation et appel de méthodes sont séparées par la balise --MethodCalls.
Figure SEQ Figure \* ARABIC 19 Exemple de gène pour la génération de cas de test unitaire
Modèle de gène pour le test unitaire. Un gène est un cas de test unitaire. Il est modélisé par une méthode composée de deux parties. Soit m1,
,mn n méthodes appelées et p1,
,pn n ensembles de paramètres effectifs pour les appels de méthode. Un gène est défini par une paire I, S (I pour initialisation, et S pour la séquence dappels) telle que G=[I, S] où S=( m1(p1),
,mn(pn)).
I : création et initialisation dinstances de la classe sous test, et dobjets nécessaires au test
S : séquence dappels de méthodes sur les objets créés
Lopération de mutation pour lalgorithme génétique peut être définie à partir de ce modèle de gène. Elle consiste à modifier les valeurs des paramètres effectifs pour un appel de méthode dans le gène.
Opération de mutation pour le test unitaire. Lopération de mutation de lalgorithme génétique modifie les paramètres effectifs dun appel de méthode dun gène, selon la règle suivante : G = [I , S] ( G = [I , Smut]
S=(m1(p1),
, mi(pi),
, mn(pn)) ( Smut=(m1(p1),
, mi(pimut),
, mn(pn))
Cette opération est importante pour générer des données qui permettent de couvrir dautres parties du code. Par exemple dans le cas dune conditionnelle dans une méthode, il faut appeler cette méthode avec au moins deux valeurs différentes, pour pouvoir passer dans les deux branches de la conditionnelle. La REF _Ref37316505 \h Figure 20 illustre un exemple pour lopération de mutation sur un gène. Dans ce cas, la valeur 1999 du premier paramètre pour lappel de la méthode set est remplacée par 1998.
Les définitions du gène et de lopération de mutation données ici, correspondent aux concepts de CasTestUnitaire et MutationUnitaire du diagramme de la REF _Ref20224830 \h Figure 17.
Figure SEQ Figure \* ARABIC 20 Exemple de mutation dun gène pour la génération de cas de test unitaires
Spécialisation pour le test système
Cette section détaille le modèle de gène et lopérateur de mutation associé dans le cas de loptimisation de cas de test systèmes. La modélisation dun cas de test système est fortement liée au format dentrée du système. Par exemple, pour létude de cas sur un parseur C#, un cas de test est un fichier source C#, qui doit être syntaxiquement correct pour la grammaire du langage. La difficulté pour appliquer lalgorithme génétique dans ce cas, était donc de pouvoir générer automatiquement des programmes correct, et pour les expériences, il a fallu écrire un générateur spécifique au langage C#. Cependant, ce générateur de données de test pour un système pourrait être ré-utilisé pour dautres études de cas, si les données en entrée peuvent être spécifiées à laide dune grammaire.
Modèle de gène pour le test système. Dans le cas particulier dun parseur, un gène est un fichier source écrit dans le langage particulier. Chaque fichier contient plusieurs constructions du langage qui correspondent à des nuds dans larbre syntaxique. Pour un fichier comportant x nuds, un gène est représenté ainsi :G=[N1,
,Nx].
Lopération de mutation peut être définie à partir de ce modèle de gène. Elle consiste à remplacer un nud par un autre dans un gène. A partir de la grammaire du langage (décrite sous forme darbre dhéritage dans la REF _Ref20534112 \h Figure 24) il est possible de vérifier que le nouveau nud est compatible avec lancien. Lopération de mutation génère ainsi un gène licite (un fichier source syntaxiquement correct pour le langage). La REF _Ref37317235 \h Figure 21 illustre un exemple pour lopération de mutation sur un gène : un nud de type foreach est remplacé par un nud de type while dans la méthode set.
Opération de mutation pour le test système. Lopération de mutation sélectionne un gène aléatoirement dans un individu et remplace un nud de ce gène par un autre :
G = [N1,
, Ni,
, Nx] ( Gmut = [N1,
, Nimut,
, Nx]
Figure SEQ Figure \* ARABIC 21 Exemple de mutation dun gène pour lé génération de cas de test système
Les définitions du gène et de lopération de mutation données ici, correspondent aux concepts de CasTestsystème et MutationSysteme du diagramme de la REF _Ref20224830 \h Figure 17.
Etudes de cas pour un algorithme génétique
Pour étudier lautomatisation de loptimisation de cas de test en utilisant un algorithme génétique, deux études de cas ont été faites, et ont été choisies pour être représentatives de langages (Eiffel, C#), de styles de programmes (pratiquement sans état interne en Eiffel) et de niveaux de test différents (unitaire, système). Avec la première, nous nous sommes intéressés à la génération de cas de test unitaires avec des classes écrites en Eiffel. Pour la seconde nous avons appliqué un algorithme génétique pour améliorer des cas de test pour système écrit en C# dans le cadre de la plate-forme .NET. Chaque étude de cas représente une catégorie particulière de logiciel. Les classes Eiffel, étudiées au niveau unitaire, manipulent peu dattributs et ont de petites méthodes. Quant au système, il représente nimporte quel logiciel qui transforme des données en entrée dans un autre format de sortie. Par exemple, la même modélisation pour lalgorithme génétique peut être utilisée pour tester des logiciels fondés sur XML comme format déchange.
Optimisation de cas de test unitaires : un exemple en Eiffel
Létude de cas pour le test unitaire est fondée sur la librairie Pylon qui offre plusieurs structures de données pour le langage Eiffel. Cette librairie est disponible à ladresse suivante : HYPERLINK "http://www.eiffel-forum.org/archive/arnaud/pylon.htm" http://www.eiffel-forum.org/archive/arnaud/pylon.htm. Pour les expériences doptimisation automatique de cas de test, nous avons choisi le paquetage de la librairie qui concerne la gestion du temps et des dates. La classe principale de ce paquetage est p_date_time, et une description complète du paquetage est donnée REF _Ref20300161 \h Figure 22.
Figure SEQ Figure \* ARABIC 22 Classes du paquetage « date-time » de la bibliothèque Pylon
Figure SEQ Figure \* ARABIC 23 Amélioration automatique du score de mutation pour 3 ensembles de cas de test
Lensemble initial de cas de test pour chacune des classes p_time, p_date_time et p_date a été écrit par un groupe détudiants. Le score de mutation pour chaque ensemble de cas de test a alors été calculé. Les résultats ainsi que le nombre de mutants générés pour calculer ces scores sont donnés dans le REF _Ref20300543 \h Tableau 2. Le nombre de mutants varie dune classe à lautre en fonction du nombre de méthodes dans les classes et de la complexité de ces méthodes. Plus il y a de code dans une classe, plus on peut insérer derreurs. De la même façon, plus le code est complexe (points de contrôle, prédicats dans ces points de contrôle, nombre de paramètres
), plus le nombre de mutants générés est grand. Par exemple, il y a moins de mutants pour la classe p_date_time car la plupart de ces méthodes se contentent de déléguer leur traitement à des méthodes des classes p_date ou p_time. Ces méthodes sont donc très simples, et peu derreurs peuvent y être injectées.
La suite de lexpérience consiste à améliorer automatiquement ces ensembles de cas de test initiaux grâce à un algorithme génétique. Lalgorithme a été exécuté pour chaque ensemble. Chaque ensemble initial contenait trois cas de test qui ont servi de gènes pour construire la population initiale de lalgorithme. Dans les trois cas, la population initiale était de 15 individus de taille 5. Pour les résultats présentés REF _Ref20301559 \h Figure 23, le taux de mutation était amélioré de 10%.
Tableau SEQ Tableau \* ARABIC 2 Scores de mutation des ensembles initiaux de cas de test pour le paquetage « date-time »
p_datep_date_timep_time# mutants générés673199275score de mutation (%)535858Optimisation de cas de test système : lexemple dun composant .NET
Figure SEQ Figure \* ARABIC 24 Parseur pour le langage C#
Nous avons expérimenté le modèle dalgorithme génétique pour lamélioration de cas de test système sur un composant .NET écrit en C#. Ce composant est un parseur pour le langage C# ADDIN EN.CITE MSDN20024716MSDN2002C# Introduction and Overview2002marchhttp://msdn.microsoft.com/vstudio/techinfo/articles/upgrade/Csharpintro.aspMSDN20026016MSDN2002.NET homepage2002http://msdn.microsoft.com/library/default.asp?url=/nhp/Default.asp?contentid=28000519[MSDN''02a; MSDN''02b] dont le diagramme de classes est donné REF _Ref20534112 \h Figure 24. Ce diagramme est composé de 32 classes qui peuvent être divisées en trois sous-ensembles principaux. Tout dabord, la classe CSNodeBuilder est la classe principale pour la construction de larbre syntaxique, et la hiérarchie dhéritage à partir de la classe CSNode représente les différents types de nuds possibles dans larbre. Enfin, les classes qui implémentent linterface NodeVisitor constituent le troisième sous-ensemble. Ces classes correspondent à lapplication du design pattern visitor ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95] et représentent différents traitements possibles sur larbre syntaxique. Par exemple, la classe TextCSPrettyPrinter implante limpression de larbre syntaxique dans un fichier au format texte. Ce composant prend un ou plusieurs fichiers source C# en entrée, et construit larbre syntaxique correspondant.
Pour létude de lalgorithme génétique sur ce système, nous avons généré 500 mutants en nous restreignant à lopérateur NOR (cf. section REF _Ref35170227 \r \h 3.1.4). Les résultats obtenus sont néanmoins intéressants puisque le score de mutation ainsi obtenu correspond au taux de couverture des instructions des cas de test générés. La plupart des mutants ont été générés sur les classes TextCSPrettyPrinter, Tokenizer et CSNodeBuilder qui exécutent les opérations les plus complexes du système. La population initiale est constituée de 12 individus de taille 4, et le score de mutation initial est de 56%. Les résultats pour lamélioration automatique du score avec un algorithme génétique sont donnés REF _Ref20537711 \h Figure 25.
Résultats et remise en cause du modèle génétique
Cette section résume plusieurs conclusions à propos de lapplication dun algorithme génétique pour loptimisation automatique de cas de test (cf. REF _Ref20301559 \h Figure 23 et REF _Ref20537711 \h Figure 25). Nous nous intéressons dabord aux intérêts de lapproche pour améliorer la qualité des classes/composants sous test. Puis, nous expliquons le manque defficacité de ce modèle pour résoudre notre problème. Quelques points particuliers sont examinés en détail : la croissance lente et irrégulière du score de mutation, le coût en termes de temps dexécution et les problèmes de calibrage du modèle.
Pour les deux études de cas, lalgorithme génétique a automatiquement amélioré la qualité des ensembles de cas de test initiaux. Ces ensembles optimisés ont ensuite été exécutés sur les classes/composants sous test. Plusieurs erreurs ont ainsi été détectées puis corrigées. Au cours du diagnostic sur les mutants vivants, nous avons repéré des mutants équivalents et du « code mort ». En effet, si les erreurs sont injectées dans des parties du code qui ne sont jamais exécutées, i.e., du « code mort », le mutant ne peut pas être tué. Malgré cette amélioration de la qualité du composant sous test grâce à lalgorithme génétique, les résultats des expériences sont décevants, tant pour la génération de cas de test unitaires (expérimentations sur les classes de gestion de date) que pour les cas de test systèmes (pour le parseur C#). En effet, dans les deux cas le score de mutation naugmente pas de manière significative (il ne dépasse jamais 90%) et le processus ne converge pas (pas de croissance stricte du score). Enfin, nous avons eu recours à des taux inhabituels pour les opérateurs de croisement et de mutation pour améliorer lefficacité de lapproche, ce qui est suffisant pour remettre en cause le modèle génétique pour notre problème. Dans la suite de cette section nous revenons sur ces différents points et proposons des solutions pour améliorer le modèle initial.
Figure SEQ Figure \* ARABIC 25 Algorithme génétique pour l'optimisation automatique de cas test pour un parseur C#
Pour rendre compte de la non-croissance stricte du score de mutation il suffit de remarquer quau cours du passage dune génération à la suivante, certains individus sont sélectionnés en fonction de leur valeur dutilité, puis ils sont reproduits, croisés et certains gènes sont mutés pour obtenir une nouvelle population. Certaines données de test utiles peuvent donc être éliminées car non-sélectionnées pour la reproduction, et de même la mutation peut détruire une donnée de test présente uniquement dans le gène muté. Le passage dune génération peut donc entraîner une perte dinformation, et le meilleur individu de la nouvelle génération peut être moins bon que celui de la génération précédente. Cette perte dinformation entraîne une amélioration lente, et même des chutes dans la courbe de lévolution du score de mutation. La mémorisation des individus sélectionnés pour la reproduction éviterait ces problèmes. En effet, mémoriser certains individus permet au score de mutation de ne pas descendre en dessous de la valeur atteinte par les individus mémorisés.
La seconde limite du modèle est liée aux coûts de paramétrage du modèle. Un algorithme génétique cherche lindividu qui a la valeur dutilité optimale, et nessaie pas doptimiser la valeur optimale globale dune population. La taille des individus doit donc être suffisamment grande, dés linitialisation de lalgorithme, pour contenir assez de gènes (cas de test) pour atteindre la valeur dutilité optimale. Or, il est très difficile de prévoir combien de cas de test seront nécessaires pour tuer tous les mutants dun composant particulier. Il faut donc commencer avec des tailles très grandes, puis paramétrer le modèle pour que le taille choisie soit suffisamment grande pour atteindre le score optimal, et cependant pas trop grande pour que lindividu généré (un ensemble de cas de test) ne soit pas trop long à exécuter et à comprendre. De plus, le paramétrage du modèle doit être refait pour chaque nouveau composant sous test. Même si cet effort de paramétrage est une contrainte pour chaque application dun algorithme génétique, il semble particulièrement contraignant dans notre cas puisque la taille de lensemble solution nest pas importante. Une adaptation du modèle consisterait à ne plus contraindre la taille de lensemble de cas de test quon essaie daméliorer. La taille de lindividu est donc particulièrement difficile à estimer, et la notion même dindividu (qui vise à générer « un bon ensemble ») apparaît inadaptée.
Le second paramètre quil faut fixer est le taux de mutation à chaque génération. Pour chacune des expériences, il a fallu fixer ce taux à une valeur largement supérieure à celle utilisée pour une application normale dun algorithme génétique. La REF _Ref20537711 \h Figure 25 montre des résultats pour des taux de mutation de 2% et 10%. Pour le taux de mutation le plus faible (qui correspond au taux habituellement utilisé pour un algorithme génétique), loptimisation automatique ne donne aucun résultat : le score de mutation ne dépasse jamais 80% et ne se stabilise jamais. Pour le taux de mutation de 10% les résultats sont meilleurs, puisque le score de mutation augmente jusquà atteindre des pics à 90%. Ce score nest jamais stabilisé pour autant. Par ailleurs, il apparaît que lopération de mutation est celle qui crée le plus dinformation puisquelle génère de nouvelles données de test, ce qui permet de générer des cas de test qui couvrent de nouvelles parties du composant sous test. Ceci explique pourquoi un taux de mutation élevé donne de meilleurs résultats, mais est en contradiction avec lapplication normale des algorithmes génétiques pour lesquels lopération de mutation est de faible importance et donc rarement utilisée.
Enfin, la troisième limite du modèle concerne lopérateur de croisement. Le problème avec cet opérateur est plus son manque defficacité pour résoudre notre problème que son paramétrage. En effet, le modèle de gène est tel que chaque gène peut être exécuté avec le composant sous test, indépendamment de tout individu. Les gènes sont indépendants les uns des autres, et lordre dans lequel ils sont exécutés na pas dimpact sur le score de lindividu, ce qui démontre que la notion dindividu comme « séquence de gènes » nest pas exploitée pour notre problème. Il apparaît donc que lopérateur de croisement nest pas efficace dans notre contexte, puisque sa fonction est de créer de linformation en réordonnant les gènes à lintérieur des individus.
Pour conclure cette discussion, il semble que le modèle dalgorithme génétique ne soit pas adapté à loptimisation automatique de cas de test. Une adaptation de ce modèle devrait prévoir une mémoire et supprimer la notion dindividu et ne garder que des gènes (cas de test). Ceci éviterait les problèmes de paramétrage pour chaque composant sous test et permettrait une croissance stable du score de mutation. Cependant, ces expériences ont montré certains aspects intéressants de ce modèle. Tout dabord le modèle de gène est clairement défini et correspond bien à ce quil faut optimiser : on peut le réutiliser. Ensuite, lopération de mutation semble être bien modélisée aussi pour introduire de linformation. Enfin, le score de mutation est un bon indicateur de lutilité dun gène pour guider la génération vers une bonne solution. La section suivante décrit un nouveau modèle prenant en compte les différentes remarques énoncées ici pour adapter lalgorithme génétique. Ce modèle est appelé « approche bactériologique » et est fondé sur le phénomène dadaptation bactériologique ADDIN EN.CITE Rosenzweig1995341Rosenzweig, Michael L.1995Species Diversity In Space and TimeCambridge University Press436[Rosenzweig''95].
Une approche adaptative : un « algorithme bactériologique »
Les expériences décrites dans la section REF _Ref43736107 \r \h 3.3 ont montré les limites de lapplication dun algorithme génétique pour loptimisation automatique de cas de test. Cette section présente une adaptation du modèle génétique pour notre problème particulier. Ladaptation essentielle consiste à mémoriser les meilleurs cas de test dune génération à lautre. Dans ce cas, il est possible de retirer les mutants tués par ces cas de test de lensemble des mutants à tuer. Ceci a pour conséquence de diminuer le temps de calcul pour une génération lorsque lensemble des mutants vivants diminue.
Même si ladaptation du modèle génétique semble fondée sur de petits changements, elle transforme complètement lidée de lalgorithme. Un algorithme génétique étudie lensemble des solutions possibles à un problème, à la recherche de lindividu optimal. A linverse, pour le nouveau processus, lensemble des solutions peut changer dune génération à lautre puisque le but (tuer tous les mutants encore vivants) peut changer dune génération à lautre. De plus, le nouveau processus ne génère pas lindividu optimal, mais un ensemble de bons individus (ceux qui ont été mémorisés au cours du processus de génération). Notre nouvelle approche est donc éloignée du modèle génétique dans ses principes. Cependant, elle correspond toujours à une analogie avec un phénomène biologique appelé « adaptation bactériologique » ADDIN EN.CITE Rosenzweig1995341Rosenzweig, Michael L.1995Species Diversity In Space and TimeCambridge University Press436[Rosenzweig''95].
Le modèle bactériologique
Le processus global
Le but de cet algorithme est de générer un ensemble de cas de test efficace et de taille raisonnable pour un programme particulier. Lensemble solution est composé dun certain nombre déléments appelés bactéries, qui correspondent aux unités atomiques du nouveau modèle. Lalgorithme prend un ensemble initial de bactéries en entrée et son évolution consiste en une série de mutations successives sur les bactéries pour explorer lensemble des solutions possibles. Lensemble solution est construit de manière incrémentale en ajoutant des bactéries qui peuvent améliorer la qualité de lensemble. Au cours de lexécution on distingue donc deux ensembles de bactéries, lensemble solution (en construction) et lensemble des bactéries potentielles.
Comme les bactéries sont les seules éléments manipulés par cet algorithme, et que ce sont des unités atomiques, lopérateur de croisement nest plus présent dans ce nouveau processus de génération. Il ny a plus que deux opérations à définir pour lalgorithme bactériologique : la fonction dutilité et lopérateur de mutation. Nous reprenons les mêmes opérateurs de mutation que dans le cas de lalgorithme génétique (changement des paramètres dappel dans le cas unitaire, et changement dun nud syntaxique dans le cas système). Pour la fonction dutilité, on distingue entre lutilité dun ensemble de bactéries, et lutilité dune bactérie qui correspond à la capacité dune bactérie à augmenter la qualité dun ensemble. La fonction dutilité pour un ensemble de bactéries est la même que pour lalgorithme génétique (puisquun individu de lalgorithme génétique est un ensemble de gènes). La fonction dutilité pour une bactérie sexprime à partir de la fonction sur un ensemble.
Fonction dutilité pour une bactérie. Soit S un ensemble de bactéries et F la fonction dutilité pour un ensemble de bactéries. La valeur dutilité f(b) pour une bactérie b, par rapport à S, est calculée de la manière suivante : f(b) = F(S(b)-F(S). Plus la bactérie peut apporter de linformation à lensemble, plus sa valeur dutilité est grande.
Figure SEQ Figure \* ARABIC 26 Le processus bactériologique
Lalgorithme évolue de manière incrémentale grâce à ces différents opérateurs. Lalgorithme calcule la valeur dutilité de chaque bactérie à chaque étape. La bactérie qui améliore le plus la qualité de lensemble solution est sélectionnée et ajoutée à lensemble. Puis les bactéries qui ont une valeur dutilité nulle sont supprimées de lensemble des bactéries potentielles. Lalgorithme sélectionne ensuite des bactéries pour la reproduction de la même manière que pour lalgorithme génétique, et applique lopérateur de mutation sur chaque bactérie sélectionnée. Le processus général est décrit REF _Ref21228815 \h Figure 26 et le texte de lalgorithme est détaillé dans la section suivante.
le texte de lalgorithme
Pour la description du texte de lalgorithme bactériologique, on suppose lexistence de la fonction select. Limplantation des fonctions Utilité et Mute dépend de lapplication particulière de lalgorithme génétique. Toutes ces fonctions sont décrites brièvement avant lalgorithme bactériologique.
// La fonction select prend un ensemble de bactéries et une séquence de réels en entrée. La séquence de réels doit être de la même taille que lensemble de bactéries. La fonction rend un individu tiré au hasard dans le bouillon, une bactérie i ayant une probabilité proba[i] dêtre tirée.
select(Ens[Bactérie] bouillon, Ens[Réel] proba) : Bactérie
//La fonction Utilité rend la valeur dutilité dun bouillon bactériologique
Utilité (Ens[Bactérie] bouillon) : Réel
//La fonction Mute rend la bactérie bact mutée
Mute (Bactérie bact) : Bactérie
//lalgorithme bactériologique utilise une variable globale pour sauvegarder les bactéries au cours de lexécution
Ens[Bactérie] solution ( ( ;
//lalgorithme bactériologique prend en entrée : un bouillon initial, un objectif, un taux de mutation, un seuil de mémorisation et nombre de bactéries à générer à chaque génération
Algo_bactériologique (Ens[Bactérie] bouillon,
Réel obj,
Réel tx_mutation,
Réel seuil_mémo,
Entier nb_bact)
début
tantque ( ! Utilité(solution) > obj) faire
solution ( solution ( select_meilleure(bouillon) ;
bouillon ( bouillon \ {b | Utilité_relative(b) seuil_mémo alors Result ( mb
sinon Result ( (
fin
générer(Entier nb_bact, Ens[Bactérie] bouillon) : Ens[Bactérie]
local Réel u_cumulée ;
Ens[Réel] proba ;
début
u_cumulée ( EMBED Equation.3 ;
(i ( [O..bouillon.taille-1], proba[i] ( pop[i]*(Utilité(pop[i])/u_cumulée)
pour i de 0 à nb_bact-1 faire
bouillon ( bouillon ( Mute(select(bouillon, proba)) ;
fait
Result ( bouillon ;
fin
//La fonction Utilité_relative rand la valeur dutilité dune bactérie relative à lensemble de bactéries solution
Utilité_relative (Bactérie bact) : Réel
début
Result ( Utilité(solution ( bact) Utilité(solution)
fin
Le modèle pour loptimisation de cas de test
La REF _Ref21231813 \h Figure 27 illustre larchitecture générale du modèle dadaptation bactériologique. Cette approche est un nouveau modèle pour loptimisation de cas de test, la classe Bactériologique hérite donc de TestOptimiseur. Une bactérie est modélisée comme un cas de test (CasTest implante linterface Bactérie), et les deux classes qui spécialisent CasTest représentent les types de cas de test aux niveaux unitaire et système. Les cas de test pour le modèle bactériologique sont les mêmes que dans le cadre génétique. Lopérateur de mutation est présent aussi dans le cas de lalgorithme bactériologique. Puisque la structure des bactéries est la même que celle des gènes, lopérateur de mutation ne change pas non plus (MutationUnitaire et MutationSystème).
Figure SEQ Figure \* ARABIC 27 Architecture générique de lapproche bactériologique pour l'optimisation de cas de test
Par ailleurs, puisque lalgorithme bactériologique ne manipule que des bactéries qui correspondent à des unités pour le modèle, la notion dindividu ainsi que les opérateurs de reproduction et croisement ont disparu. Labandon de lopérateur de croisement est une différence majeure avec le modèle génétique. Ceci correspond à une évolution qui nous semblait nécessaire au regard des résultats de lalgorithme génétique, puisque cet opérateur nétait pas efficace pour la convergence vers une solution optimale (cf. section REF _Ref21234826 \r \h 3.3.3).
Cette approche, comme la précédente, nécessite une fonction dutilité pour sélectionner les meilleures bactéries qui sont mémorisées. Puisquune bactérie est modélisée par un cas de test, le score de mutation est utilisé comme valeur dutilité pour un gène.
Enfin, deux autres différences apparaissent par rapport au modèle précédent : lapparition dune Mémoire, et deux associations vers la classe Mutant au lieu dune. Lalgorithme bactériologique utilise une mémoire qui contient, à un instant donné, les meilleures bactéries produites au cours des générations précédentes. Dautre part, lalgorithme génétique calculait le score de mutation des cas de test avec tous les mutants à chaque génération, la classe Génétique navait donc quune association vers la classe Mutant représentant lensemble des mutants générés pour le composant sous test. Dans le cas de lalgorithme bactériologique, certaines bactéries sont sauvegardées au cours de lexécution. On distingue alors les mutants qui sont tués par les bactéries sauvegardées, des mutants qui nont pas encore été tués par une bactérie. Il y a donc deux associations de la classe Bactériologique vers la classe Mutant, représentant les ensembles de mutants tués et vivants.
De nouveaux résultats
Figure SEQ Figure \* ARABIC 28 Résultats de l'algorithme bactériologique pour l'amélioration automatique de cas de test unitaires
Les REF _Ref21324203 \h Figure 28 et REF _Ref21324204 \h Figure 29 donnent lévolution du score de mutation pour les cas de test unitaires des trois classes p_time, p_date et p_date_time, ainsi que pour les cas de test du parseur C#. Le score de mutation de ces ensembles de cas de test a été amélioré automatiquement grâce à un algorithme bactériologique. Pour ces expérimentations, deux paramètres on dû être fixés : le nombre maximal de bactéries sauvegardées lors du passage dune génération à lautre, et la taille dune bactérie. Puisque lensemble initial de bactéries était petit dans tous les cas (entre 3 et 10 bactéries), nous avons décidé de ne sauvegarder quune seule bactérie à la fin dune génération. La taille dune bactérie correspond à des paramètres différents en fonction du type de cas de test généré (cf. définitions suivantes). Une discussion sur le paramétrage de cette taille dans le cas de létude du parseur est présentée section REF _Ref43736165 \r \h 1.1.
Figure SEQ Figure \* ARABIC 29 Résultats de l'algorithme bactériologique pour l'amélioration automatique de cas de test systèmes
Taille dun cas de test unitaire. Soit B=[I,S] un cas de test unitaire pour lequel S est un ensemble dappels de méthodes sur des instances de la classe sous test. La taille dun tel cas de test est le cardinal de lensemble S.
Taille dun cas de test système. Soit B=[N1,
,NX] un cas de test pour un parseur contenant X constructions du langage (X nuds de larbre syntaxique). Alors le nombre X est aussi la taille de la bactérie modélisée par ce cas de test.
Discussion et validation du modèle bactériologique
Cette section présente les gains en performance de lalgorithme bactériologique par rapport à lalgorithme génétique pour la génération de test. Nous discutons ensuite de différents problèmes qui peuvent apparaître en utilisant cet algorithme.
Les performances
Lalgorithme bacétriologique converge plus rapidement que le précédent. Le REF _Ref21343883 \h Tableau 3 résume les performances des deux approches pour le parseur C#. Cette table donne le nombre de générations nécessaire pour atteindre le score de mutation donné dans la deuxième colonne. Lalgorithme bactériologique converge beaucoup plus rapidement que le génétique : 30 générations au lieu de 200. Cependant, comme les calculs effectués au cours dune génération ne sont pas les même dans les deux approches, la dernière colonne du tableau donne des chiffres comparables : le nombre de programmes mutants exécutés pour atteindre le score de mutation maximal. Ces chiffres correspondent à une meilleure estimation que le nombre de générations de la complexité de chaque approche. En effet lexécution dun mutant avec un cas de test est la tâche la plus coûteuse dans les deux cas.
Tableau SEQ Tableau \* ARABIC 3 Comparaison des performances des algorithmes
Algorithme# générationsscore (%)# mutants exécutésGénétique20085480000Bactériologique309646375Ces nouvelles expériences ont donné dautres résultats intéressants. Tout dabord, la mémorisation des meilleures bactéries évite les creux dans lévolution du score de mutation, rendant lalgorithme bactériologique plus rapide que le précédent. Ensuite, lapproche bactériologique est plus facile à paramétrer grâce à la suppression de plusieurs paramètres (taille des individus, et taux de croisement parmi ces individus). Ceci rend cette approche plus facile à réutiliser pour loptimisation automatique de cas de test. Lalgorithme est aussi davantage contrôlable puisque lévolution de lalgorithme est moins aléatoire.
Limites de lalgorithme
Deux inconvénients apparaissent en passant de lalgorithme génétique à lapproche bactériologique. Premièrement, la possible perte dinformation due au fait que les bactéries dune nouvelle génération sont générées à partir des meilleures bactéries uniquement. En effet, certaines informations peuvent nêtre présentes que dans les bactéries moins efficaces. Pour résoudre cette perte dinformation potentielle, une solution consiste à tirer au hasard une bactérie sauvegardée pour générer une nouvelle population si le score stagne trop longtemps.
Ensuite, lensemble final de cas de test peut ne pas être minimum en appliquant lalgorithme bactériologique. Par exemple, neuf cas de test ont été générés dans le cas du parseur C# au lieu de quatre avec lalgorithme génétique. Or, il est important davoir un ensemble de cas de test de taille raisonnable pour le test de non-régression, puis lors de la maintenance du système.
Deux techniques permettraient de résoudre ce problème : minimiser lensemble de cas de test générés à la fin de lalgorithme ou sauvegarder moins de bactéries. Il est possible de sauvegarder moins de bactéries en atteignant le même score de mutation final. Pour cela, il faut fixer un seuil au-delà duquel les bactéries sont sauvegardées, ce qui force lalgorithme à générer plus de nouvelle information avant la mémorisation. Lensemble final atteint donc des scores de mutation similaires avec moins de bactéries. Des expériences avec différents seuils pour la sauvegarde sont détaillées dans la section REF _Ref43736165 \r \h 1.1.
Le problème de la minimisation dun ensemble de cas de test, consiste à trouver un sous-ensemble de cas de test assurant un pouvoir de détection derreur équivalent à celui de lensemble initial. Dans le cas particulier de lanalyse de mutation, ce problème peut se ramener à la minimisation de la matrice couverture des mutants par les cas de test. Cette matrice sobtient en calculant les signatures complètes de toutes les bactéries sur tous les mutants. Pour k bactéries et n mutants, on obtient une table T de booléens de taille (n+1)*k telle que:
EMBED Equation.3 , T(Bi , Mj) = 1 si la bactérie Bi tue mutant Mj
0 sinon
EMBED Equation.3 , cest-à-dire que T(n+1 , Mj) vaut 1 si le mutant Mj est tué par une bactérie. Cette dernière ligne correspond donc à la signature globale de lensemble de n bactéries. La REF _Ref21507840 \h Figure 30 donne un exemple de matrice obtenue. La minimisation de la matrice permet dobtenir lensemble de cas de test minimal nécessaire pour tuer tous les mutants.
Figure SEQ Figure \* ARABIC 30 Exemple de table pour le calcul de lensemble minimal de tests
Les nouveaux résultats montrent que les adaptations de lalgorithme génétique qui avaient été détectées comme nécessaires au cours de la discussion de la section REF _Ref21234826 \r \h 3.3.3 sont de bonnes heuristiques pour résoudre notre problème. Lapplication de ces heuristiques nous a conduits à définir un nouveau modèle appelé modèle bactériologique. Cet algorithme apparaît plus souple et stable que lalgorithme génétique. La section suivante détaille les expériences conduites sur le parseur C# pour le paramétrage du paramètre principal du modèle bactériologique, à savoir la taille dune bactérie. Dans un second temps, et pour que létude expérimentale soit aussi exhaustive que possible, nous avons cherché sil existait un algorithme intermédiaire plus efficace entre lalgorithme génétique sans mémorisation et lalgorithme bactériologique qui mémorise toute bactérie améliorant le score de mutation de lensemble.
Paramétrage des modèles
Paramétrage du modèle bactériologique
Lunique paramètre quil faut fixer pour lalgorithme bactériologique est la taille des bactéries. Dans le cas du test unitaire, cette taille est définie par le nombre dappels de méthode inclus dans un cas de test (cf. section REF _Ref21746244 \r \h 3.4.2). Pour le test système, la taille dune bactérie dépend du format des données dentrée pour le programme sous test. Dans le cas du parseur C#, la taille est donnée par le nombre de nuds de larbre syntaxique contenu dans un cas de test. Cette modélisation de la taille dune bactérie pourrait sappliquer à tout logiciel qui transforme des données dun format à un autre, notamment des logiciels fondés sur XML. La REF _Ref22437595 \h Figure 31, donne la courbe en trois dimensions et sa projection plane (des niveaux de gris exprimant les variations du score de mutation) pour la relation entre la taille de la bactérie, le score de mutation et le nombre de générations. Notons que cette courbe garde la même allure, même en répétant les expérimentations (doù la ligne médiane en noir sur la figure). Les conclusions sont les suivantes :
si la bactérie est trop petite ( val_seuil alors sauvegarde B
Le type de lalgorithme dépend de la valeur de seuil:
si val_seuil = 100 alors pur génétique
si val_seuil = 0 alors pur bactériologique
si 0 < val_seuil < 100 alors approche mixte
Deux critères sont pris en compte pour déterminer la meilleure valeur pour le seuil de mémorisation:
minimisation du nombre de bactéries sauvegardées à la fin du processus, pour minimiser le temps dexécution et la difficulté dinterprétation des cas de test. La REF _Ref22438746 \h Figure 32 montre limpact de la valeur de seuil sur le nombre de bactéries mémorisées.
minimisation du nombre de générations pour atteindre le score de mutation optimal. La REF _Ref22438761 \h Figure 33 présente limpact de la valeur de seuil sur la vitesse de convergence.
Figure SEQ Figure \* ARABIC 34 Évolution du score de mutation avec une approche mixte
La REF _Ref22438746 \h Figure 32 montre que le nombre de bactéries sauvegardées décroît régulièrement avec laugmentation de la valeur du seuil de mémorisation, la REF _Ref22438761 \h Figure 33 indique que la vitesse de convergence augmente lorsque le seuil dépasse 30%. Un compromis peut être trouvé pour minimiser ces deux valeurs lorsque le seuil se situe entre 20 et 30%. Dans ce cas, la vitesse de convergence est satisfaisante (autour de 30 générations pour atteindre le score optimal) et lensemble final de bactéries contient 7 bactéries. La REF _Ref22438968 \h Figure 34 montre les résultats obtenus pour une valeur de seuil de 25%.
En conclusion, lapproche mixte permet de réduire la taille de lensemble final de cas de test de 10 à 7. Par contre, le score de mutation de 95% nest atteint quaprès 25 générations au lieu de 20 dans le cas de lalgorithme bactériologique (cf. REF _Ref21324204 \h Figure 29). Le gain de cette approche mixte nous semble donc peu significatif par rapport à leffort de paramétrage quil nécessite. En effet, de nombreuses expérimentations sont nécessaires pour trouver un bon compromis pour la valeur de seuil de mémorisation. A linverse, lapproche bactériologique semble plus facile à réutiliser et généraliser.
Conclusion
Lefficacité des cas de test pour détecter des erreurs dans un composant est essentielle pour la qualité du composant. La méthode de conception, proposée au chapitre précédent, prévoit dévaluer lefficacité des cas de test pour un composant grâce à lanalyse de mutation. Notre expérience montre que les cas de test fournis par le testeur atteignent un score de mutation entre 50 et 70%, mais quaméliorer ce score pour dépasser 90% est très coûteux en temps et en effort. Dans ce chapitre, nous avons donc étudié lamélioration automatique dun ensemble initial de cas de test.
La complexité de ce problème doptimisation nous a tout dabord amenés à utiliser un algorithme génétique pour le résoudre. Cependant, dés la modélisation du problème, nous avons dû adapter certains aspects de lalgorithme (taux de mutation, modélisation du gène) de manière tellement forte que lalgorithme séloignait dun algorithme génétique classique. Ensuite, les résultats expérimentaux étant décevants, notamment à cause de certaines contraintes liées à lalgorithme (pas de mémorisation, recherche dun individu unique, croisement), nous avons proposé un autre algorithme.
Cet algorithme original, inspiré du phénomène dadaptation bactériologique, et appelé algorithme bactériologique, permet de résoudre les problèmes rencontrés avec lalgorithme génétique lors de loptimisation de cas de test. Les apports importants de cet algorithme, sont la recherche dun ensemble de solutions plutôt quune solution unique et la suppression de lopération de croisement pour ne garder que la mutation comme opérateur pour la génération de nouvelles informations. Du fait de la limitation du nombre de paramètres, cet algorithme est plus facile à réutiliser et à généraliser. Pour loptimisation dun ensemble de cas de test, il a permis daméliorer le score de mutation dun ensemble initial rapidement, en générant un nombre raisonnable de cas de test supplémentaires.
De nouveaux travaux sont en cours pour la validation de cet algorithme sur de nouvelles études de cas et avec dautres fonctions dutilité (e.g., couverture de code). Ces études devraient permettre aussi de généraliser le processus de génération bactériologique, et daffiner les différents paramètres du modèle.
Robustesse et diagnosabilité : impact de la conception par contrat sur un assemblage de composants
La conception par contrat permet de définir précisément les obligations et devoirs des différents composants dans un système logiciel. Cette technique permet notamment dembarquer la spécification de chaque opération dans le programme et de vérifier la cohérence de létat interne du système au cours de lexécution. De nombreux travaux reconnaissent limportance dune telle approche pour la conception de systèmes fiables, et pour la détection derreurs ADDIN EN.CITE Voas19991060Voas, J. M.Kassab, Lora1999Using Assertions to Make Untestable Software More TestableSoftware Quality Professional14SeptemberZ:\biblio-bb\Testabilité\AssertionsForTestability.pdfRosenblum1995330Rosenblum, David S.1995A Practical Approach to Programming With AssertionsIEEE Transactions on Software Enginnering21119 - 31JanuaryCarrillo-Castellon19961220Carrillo-Castellon, M. Garcia-Molina, J.Pimentel, E.Repiso, I.1996Design by contract in SmalltalkJournal of Object-Oriented Programming8723 - 38NovemberFindler20011213Findler, Robert BruceFelleisen, Matthias2001Contract Soudness for Object-Oriented LanguagesOOPSLA 2001Tampa Bay, FL, USA1 - 15OctoberZ:\biblio-bb\soft-contracts\ContractsSoundnessForOO.pdfNordby2002243Nordby, J. EivindBlom, MartinBrunstrom, Anna2002On the Relation between Design Contracts and Errors: a Software Development StrategyECBSLund, SwedenApril[Rosenblum''95; Carrillo-Castellon''96; Voas''99; Findler''01; Nordby''02], mais lestimation de lapport qualitatif des contrats pour un programme est rarement étudiée.
Dans le cadre des travaux présentés dans cette thèse, nous étudions la contribution des contrats en tant quoracle pour le test et leur impact sur la robustesse et la diagnosabilité. Tout dabord, nous analysons la capacité des contrats à détecter des erreurs dans un composant à travers une mesure de robustesse. Nous proposons aussi un modèle qui prend en compte le fait que, dans le cadre dun assemblage de composants, les contrats embarqués dans un composant peuvent détecter des erreurs présentes dans un autre composant fournisseur de services. Ensuite, nous étudions limpact des contrats pour le diagnostic, i.e., lamélioration de la localisation dune faute en présence de contrats. Pour cela, nous proposons un modèle de la mesure de diagnosabilité en présence de contrats.
Les travaux présentés dans ce chapitre concernent de manière équivalente tant le test de programme que la mesure de logiciels. Une grande partie des travaux présentés ici est donc dédiée à lélaboration précise et non ambiguë dune mesure. Pour cela, nous proposons une méthodologie générique pour lélaboration dune mesure, puis nous définissons les mesures de robustesse et diagnosabilité dans ce cadre. Lapproche consiste essentiellement à identifier les attributs participant à la définition de la mesure, puis à définir un modèle en fonction de ces attributs. Enfin, des expériences permettent de paramétrer ces attributs et dobserver ensuite le comportement de la mesure pour différentes valeurs des attributs obtenues.
Conception par contrat et élaboration dune mesure
Figure SEQ Figure \* ARABIC 35 Les contrats pour la diagnosabilité
Les contrats pour la robustesse et la diagnosabilité
Nous voulons maintenant mesurer lapport dune conception par contrat et déterminer leur influence sur la qualité finale du logiciel de deux manières :
puisquune exception est soulevée lorsquun contrat est violé, ceux-ci se comportent comme des assertions classiques : un état erroné au cours de lexécution peut être détecté automatiquement, et la défaillance qui se serait produite peut être évitée. De manière intuitive, il apparaît que les contrats participent à la robustesse du logiciel. La question est alors de savoir dans quelles proportions les contrats participent à la robustesse en fonction de leur « force » et de leur nombre
lorsquun contrat est violé, il indique lendroit dans le code où un état erroné a été détecté. Si aucun contrat nest présent dans le logiciel, la défaillance est détectée plus tard, et se propage jusquà la sortie du système. Puisque la zone pour le diagnostic (le nombre dinstructions parmi lesquelles il faut chercher lerreur) est réduite en présence de contrats, il apparaît que les contrats simplifient la tâche de diagnostic. La question est alors de savoir dans quelles proportions les contrats participent à la diminution de leffort de diagnostic en fonction de leur « force » et de leur nombre
Comme le montre la REF _Ref32896803 \h Figure 35, un système conçu par contrats devrait permettre une détection précoce dune faute (pendant quelle se propage vers la sortie du logiciel). Ceci aiderait à la localisation de la faute en réduisant la zone pour le diagnostic. Ce qui rend les contrats intéressants, cest que leur efficacité ne dépend pas dune éventuelle répartition ou distribution de lexécution du logiciel. Même si les fautes sont difficiles à localiser dans un environnement réparti, les contrats ont de grandes chances de détecter une erreur à proximité de linstruction erronée.
Elaboration dune mesure
La littérature insiste sur les difficultés détablir une mesure valide ADDIN EN.CITE Shepperd1993821Shepperd, MartinInce, D.1993Derivation and Validation of Software MetricsNew York, N.Y., USAOxford University Press1670198538421Kitchenham1995810Kitchenham, B.Pfleeger, S.L.Fenton, N.1995Towards a framework for software measurement validationIEEE Transactions on Software Enginnering2112929 - 944DecemberFenton1986800Fenton, N.E.Whitty, R.W.1986Axiomatic Approach to Software Metrication through Program DecompositionComputer Journal294330 - 339AugustBriand1996790Briand, LionelMorasca, S.Basili, Victor S.1996Property-based Software Engineering MeasurementIEEE Transactions on Software Enginnering22168 - 86January[Fenton''86; Shepperd''93; Kitchenham''95; Briand''96]. Puisque les mesures que nous étudions ici apparaissent comme assez abstraites (au sens où il ny a pas dattribut mesurable immédiat qui la détermine), nous proposons une axiomatisation de ces mesures ADDIN EN.CITE Shepperd1993821Shepperd, MartinInce, D.1993Derivation and Validation of Software MetricsNew York, N.Y., USAOxford University Press1670198538421[Shepperd''93]. La REF _Ref22455513 \h Figure 36 illustre le processus pour lélaboration dune mesure. Tout dabord, le facteur qui va être mesuré est décrit de manière informelle, et les attributs mesurables qui linfluencent sont identifiés. Puis les propriétés intuitives sur le comportement du facteur doivent être exprimées en fonction des attributs choisis : cest ce que nous appelons les axiomes. Un axiome est une propriété attendue de la mesure qui apparaîtra dans le modèle mathématique. Cest ce qui fait le lien entre lapproche intuitive et lapproche formelle nécessaire à la validation théorique. Un modèle formel de la mesure peut alors être proposé. Ce modèle complète les axiomes. Le rôle de laxiomatisation est double : elle offre un cadre commun pour lévaluation de plusieurs mesures et elle permet de passer de lidée intuitive à lélaboration de la mesure. Un aspect secondaire non négligeable consiste à faire entrer dans le champ de la discussion la manière de mesurer. Grâce à lexplicitation des propriétés intuitives et à leur correspondance dans un modèle mathématique, les limites de la mesure apparaissent clairement.
Puisque les axiomes formalisent lessentiel des propriétés attendues dune mesure, ils définissent quels systèmes peuvent être comparés, et les caractéristiques génériques que ces mesures doivent vérifier. Les axiomes constituent la base de lévaluation théorique que nous avons conduite pour vérifier ladéquation de la mesure proposée avec un logiciel réel. Lévaluation théorique précède lévaluation empirique puisquelle est moins coûteuse en temps et quelle permet de montrer la cohérence interne du modèle.
Figure SEQ Figure \* ARABIC 36 Élaboration dune mesure
Mesure de la robustesse
Nous présentons ici, un modèle qui lie la robustesse dun composant logiciel et ses contrats. Une mesure de lefficacité des contrats est proposée et lamélioration de la robustesse grâce aux contrats est expliquée.
Définitions
Avant de définir la mesure de robustesse, nous revenons sur les notions de faute, erreur et défaillance qui sont utilisées par la suite. Comme nous lavons dit à la section REF _Ref42515185 \r \h 2.1.1, une faute désigne la cause dune erreur, une erreur est la partie de létat dun système qui peut entraîner une défaillance, et une défaillance est un événement survenant lorsque le service délivré dévie de laccomplissement de la fonction du système. Dans le contexte particulier de létude présentée ici, une faute est une instruction ou un ensemble dinstructions (ou même labsence dinstruction) qui peut entraîner une erreur. Une erreur est létat dun objet (lensemble des valeurs des attributs de lobjet) dans le système qui peut déclencher une défaillance. Par exemple, observons une version erronée de la classe BankAccount donnée REF _Ref32488155 \h Figure 37. Linstruction balance = balance sum de la méthode deposit, devrait être balance = balance + sum. Cette instruction correspond à une faute si elle est exécutée, lattribut balance de la classe BankAccount se voit affecter une mauvaise valeur. Létat de lobjet après lexécution de cette méthode est une erreur. Cette erreur peut effectivement entraîner une défaillance puisque la valeur de balance nest pas cohérente avec la valeur attendue spécifiée par la postcondition.
Figure SEQ Figure \* ARABIC 37 Exemple pour une faute, une erreur et une défaillance
Il est maintenant possible de définir la robustesse, tout dabord de manière intuitive, puis de façon plus précise en distinguant différent niveaux de granularité pour la mesure de robustesse dans le contexte particulier de la conception par contrat.
Robustesse (définition informelle) : La robustesse dun composant Ci est définie comme la capacité de ce composant à fonctionner même dans des conditions anormales. Cette capacité permet déviter les défaillances catastrophiques. La robustesse peut aussi être vue comme la probabilité quune erreur soit détectée et traitée par un mécanisme dexception sachant quune défaillance se produit certainement sinon.
Nous considérons que lattribut principal pour la robustesse est laptitude dun logiciel à détecter des erreurs internes.
Robustesse isolée (RobIsolée(Ci)): la robustesse isolée RobIsolée(Ci) dun composant Ci dans un système S est définie comme la probabilité quune erreur soit détectée par Ci sachant que cette erreur aurait provoqué une défaillance. Inversement, la « faiblesse » dun composant est égale à la probabilité que lerreur ne soit pas détectée.
Cette définition introduit la notion derreur interne. Une erreur interne dans un composant Ci, correspond à une valeur erronée dun ou plusieurs attributs du composant Ci. Nous nous intéressons aux contrats et aux assertions pour la détection derreurs. Une erreur est donc considérée comme détectée si un contrat est violé à cause de cette erreur. Par exemple, la faute de la REF _Ref32488155 \h Figure 37 entraîne une erreur interne dans la classe BankAccount qui est détectée par la postcondition de la méthode deposit. De nombreux composants ne peuvent pas être exécutés directement (classes abstraites ou génériques, frameworks). Néanmoins, des contrats peuvent être définis pour ces composants, permettant ainsi la détection derreurs internes de leurs instances ou de leur client.
Robustesse globale (Rob) : la robustesse globaleRob(S) dun système S composé dun ensemble de composants interconnectés est la probabilité quune erreur interne soit détectée par un de ces composants.
Autrement dit, la robustesse globale correspond à la probabilité quun contrat détecte lerreur lorsque létat du programme devient erroné. Une faute dans un composant utilisé par un système peut être détectée soit par le composant lui-même soit par un de ces clients ou une spécialisation de ce composant. Il apparaît alors que la robustesse globale ne peut pas être déduite directement de la robustesse locale. Notre hypothèse est quil existe une relation entre la robustesse locale et la robustesse globale mais que des informations supplémentaires sur larchitecture sont nécessaires pour obtenir la valeur de la robustesse globale. En nous appuyant sur un diagramme de classes UML pour décrire larchitecture dun logiciel, nous repérons les attributs pertinents qui peuvent être extraits de larchitecture. Ces attributs permettent de calculer la robustesse globale fondée sur la robustesse locale. Nous avons maintenant besoin de définir la robustesse locale dun composant utilisé dans un système.
Robustesse locale (RobLoc) : la robustesse locale RobLoc(Ci,S) dun composant Ci dans un système S est définie comme la probabilité quune erreur soit détectée par Ci ou un autre composant dans le système S, sachant que cette erreur aurait provoqué une défaillance.
Il est nécessaire de faire la distinction entre la robustesse isolée et la robustesse locale. En effet, si une faute dans un composant Ci nest pas détectée par ses propres contrats, elle peut se propager à travers le système. Lerreur peut alors être détectée par les contrats dautres composants. La robustesse dun composant dans un assemblage de composants est donc différente de la robustesse du composant en-dehors de tout contexte. La robustesse locale et la robustesse isolée sont des mesures locales à un composant, alors que la robustesse globale concerne tout le système. Laxiomatisation est ainsi décomposée en axiomes locaux et globaux.
Axiomatisation
Les axiomes formalisent les propriétés attendues dune mesure. A partir des définitions énoncées dans la section précédente, nous proposons trois ensembles daxiomes : les axiomes globaux, les axiomes locaux (pour la robustesse locale et isolée) et les axiomes qui lient les mesures globales aux mesures locales. Nous nous appuierons ensuite sur ces axiomes pour la vérification théorique de la cohérence entre les mesures et les propriétés attendues.
Axiomes locaux de robustesse
ALR1 Comparaison entre composant. Tous les composants dun système sont comparables en termes de robustesse locale ou isolée.
ALR2 Composant sans contrat (ni assertion). Les composants qui nont pas de contrats (ni dassertions ni aucun autre mécanisme de détection derreur) ont une robustesse locale nulle.
Les axiomes suivants définissent le comportement attendu, de manière intuitive, lors de certaines opérations sur le modèle conceptuel : assemblage de systèmes, ajout de contrats et amélioration des contrats.
Assemblage : regroupe toute opération qui consiste à connecter deux systèmes pour en créer un nouveau (par exemple en utilisant lhéritage, ou des relations dutilisation)
Ajout de contrats : lopération qui consiste à définir des contrats pour un composant dans un système
Amélioration des contrats : opération qui consiste à ajouter une nouvelle clause dans un contrat existant, pour vérifier une propriété qui nétait pas encore vérifiée. Un contrat est donc amélioré uniquement sil vérifie plus de propriétés pour un composant.
Par exemple, une précondition pour la méthode withdraw de la classe BankAccount peut être:
context BankAccount::withdraw(amount:float):void
pre amount>0
Une amélioration de cette précondition peut consister en la précondition suivante
context BankAccount::withdraw(amount:float):void
pre amount>0 and amount ( balance-overdraft
ALR3 Assemblage de systèmes. La robustesse isolée dun composant inclus dans un système S1 nest pas modifiée par lassemblage avec un système S2. La robustesse locale du composant ne peut pas diminuer.
ALR4 Ajout de contrat. La robustesse locale et la robustesse isolée dun composant ne peuvent pas décroître lors de lajout de contrat dans le système.
ALR5 Amélioration des contrats. Lamélioration dun contrat dans un composant Ci doit améliorer sa robustesse locale et sa robustesse isolée. La robustesse locale et la robustesse isolée des autres composants ne peut pas diminuer.
Axiomes globaux de robustesse
AGR1 Comparaison entre systèmes. Deux systèmes sont toujours comparables en termes de robustesse.
AGR2 Assemblage de systèmes. La robustesse globale dun système obtenu par lassemblage de deux systèmes S1 et S2 ne peut pas être plus faible que la plus faible des robustesses de S1 et S2. Ainsi, la robustesse dun système S3, composé par lassemblage des systèmes doit vérifier : Rob(S3) ( min(Rob(S1), Rob(S2)).
AGR3 Ajout de contrat. Pour un système, sa robustesse globale ne peut pas décroître lors de lajout dun contrat.
Hypothèses et modèle mathématique
Selon notre définition, la robustesse dun composant en-dehors de tout système correspond à lefficacité de ses contrats, et nous lavons nommée la robustesse isolée. La robustesse dun composant utilisé dans un système est améliorée par les contrats de ses clients. Nous utilisons la notion de dépendance de test, introduite dans ADDIN EN.CITE Le Traon2000100Le Traon, YvesJéron, T.Jézéquel, Jean-MarcMorel, P.2000Efficient OO Integration and Regression TestingIEEE Transactions on Reliability49112 - 25March[Le Traon''00a], pour expliquer la relation entre un composant et ses clients pour la robustesse.
Figure SEQ Figure \* ARABIC 38 Dépendance de test
Dépendance de test. Un composant Ci dépend de Cj pour le test sil utilise des services fournis par Cj. Cette relation de dépendance est notée : CiRTDCj. Nous avons besoin dexprimer cette notion de clientèle (qui sétend à lhéritage) pour établir le modèle de la mesure de robustesse globale en fonction des robustesses isolées de larchitecture.
Par exemple sur la REF _Ref22522896 \h Figure 38, Ci utilise les services de Cj ( REF _Ref22522896 \h Figure 38), on note alors CiRTDCj.
Probabilité Det(Ci, Cj). Si CiRTDCj, alors la probabilité que les contrats de Ci détectent une erreur lors dune utilisation par Cj est notée Det(Ci, Cj). Le cas limite pour cette mesure est Det(Ci, Ci) qui correspond à la robustesse isolée du composant Ci. Par ailleurs, si Ci et Cj sont dans des systèmes différents (S1 et S2), on notera cette probabilité Det((Ci, S1), (Cj, S2)).
Remarque : une probabilité Det(Ci, Cj) non nulle signifie que la robustesse dun composant embarqué dans un système sera améliorée grâce au couplage entre les composants. Il est alors intéressant de noter que dans le cas dun système conçu avec des contrats, un couplage important va permettre daméliorer la qualité globale du système alors que cest habituellement considéré comme un signe dune mauvaise conception.
Dans la section suivante, nous proposons une méthode pour évaluer la robustesse dun composant utilisé dans un système, ainsi que pour calculer la probabilité Det(Ci, Cj).
Même si la relation de dépendance de test est transitive, nous ne considérons que les fautes détectées par un composant directement dépendant du composant erroné. Lhéritage est un cas spécial de dépendance de test dont limpact sur la robustesse ne nous apparaît pas prévisible intuitivement. Aussi, au cours des expérimentations, avons-nous considéré deux hypothèses : une première approche pessimiste qui considère que la probabilité Det(Ci, Cj) est nulle dans le cas de lhéritage, et une seconde pour laquelle nous considérons que la probabilité est la même dans le cas de lhéritage que dans le cas dune relation de clientèle.
La robustesse RobLoc(C, S) (1- FaibLoc(C, S)) dun composant C dans un système S est la probabilité quune faute dans C soit détectée par les contrats de C ou par une utilisation de C par dautres composants. Pour calculer cette probabilité, nous calculons FaibLoc(C, S). La probabilité FaibLoc(C, S) correspond à la probabilité quune faute due à C ne soit pas détectée par C multipliée par la probabilité que lerreur ne soit pas détectée par les clients de C.
Robustesse locale. La robustesse locale est calculée de la manière suivante : RobLoc(Ci, S) (1- FaibLoc(Ci,S)) où EMBED Equation.3 avec k / Ck RTD Ci.
Robustesse globale. La robustesse globale Rob dun système sexprime à partir de la robustesse locale de la manière suivante :
EMBED Equation.3
Pour le calcul de Rob(S), ProbErr(Ci,S) est la probabilité quune défaillance provienne de Ci sachant quune défaillance apparaîtra forcément. La complexité du composant est une approximation de cette probabilité. Par ailleurs, on note |S| le nombre de composants dans le système S.
Démonstration des axiomes
La démonstration des axiomes locaux étant assez immédiate, nous détaillons ici la démonstration des deux axiomes globaux principaux : AGR2 et AGR3. Nous rappelons dabord, les notations pour les mesures et définissons les profils :
Rob(S) la robustesse globale dun système S.
Rob(S) : Système ( [0
1]
RobLoc(C, S) la robustesse locale dun composant C dans un système S.
RobLoc(C, S) : Composant ( Système ( [0
1]
FaibLoc(C, S) lopposée de la robustesse locale de C dans S.
FaibLoc(C, S) : Composant ( Système ( [0
1]
ProbErr(C, S) la probabilité quune erreur soit localisée dans le composant C dun système S. Nous faisons lhypothèse que cette probabilité est uniforme pour un système : ProbErr(C, S) = 1/|S|.
Det(Ci, Cj) la probabilité quun composant Ci détecte une erreur dans Cj.
Det(Ci, Cj) : Composant ( Composant ( [0
1]
Preuve de laxiome AGR2 : assemblage de deux systèmes
Figure SEQ Figure \* ARABIC 39 Assemblage de deux systèmes
Nous voulons montrer ici, que la robustesse dun système S3, composé par lassemblage des systèmes S1 et S2, nest pas inférieure à la plus petite des robustesses de S1 et S2. En dautres termes, nous voulons vérifier que : Rob(S3) ( min(Rob(S1), Rob(S2)).
Lassemblage de deux systèmes S1 et S2 se fait par lajout dune relation entre un nud de S1 et un nud de S2 comme lillustre la REF _Ref37483981 \h Figure 39. La formule générale qui exprime la robustesse de S3 est :
EMBED Equation.3
Comme il ny a pas dajout de nouveau nud lors de lassemblage de deux systèmes, on a : |S3| = |S1|+|S2|, et donc :
EMBED Equation.3 Posons A et B tels que :
EMBED Equation.3 , et EMBED Equation.3
Lajout de larc (Ci, Cj) a un impact sur la robustesse locale de Ci, mais ne modifie rien pour Cj. Lajout de cet arc na donc pas deffet sur le système S2, auquel appartient Cj, on simplifie donc dabord le terme B qui concerne S2.
EMBED Equation.3
Or, FaibLoc(C,S2) = FaibLoc(C,S3) pour tout composant C dans S2, donc :
EMBED Equation.3 et donc EMBED Equation.3 .
On sintéresse maintenant au terme A. Tout dabord, de la même façon que pour le terme B, on obtient : EMBED Equation.3 .
Maintenant, si on désigne C1 par le composant sur lequel la relation est rajoutée, on a alors : ( i ( [2
| S1|], FaibLoc(Ci,S1) = FaibLoc(Ci,S3) et donc :
EMBED Equation.3 .
EMBED Equation.3
Comme 1-Det((C1,S2), (C1,S1)) ( 1, alors :
FaibLoc(C1,S3) ( FaibLoc(C1,S1)
( EMBED Equation.3
Doù : EMBED Equation.3 et donc EMBED Equation.3
Maintenant que nous avons simplifié les termes A et B, revenons à lexpression de la robustesse globale de lassemblage S3.
Rob(S3) = 1-A-B,
EMBED Equation.3
Donc pour montrer que Rob(S3) ( min(Rob(S1), Rob(S2)), il faut montrer :
EMBED Equation.3
Comme |S3| = |S1|+|S2|, lexpression ci-dessus peut se réécrire de la manière suivante:
EMBED Equation.3
Considérons maintenant min(Rob(S1), Rob(S2)) = Rob(S1) on veut maintenant montrer :
EMBED Equation.3 Comme min(Rob(S1), Rob(S2)) = Rob(S1), alors Rob(S2) - Rob(S1) ( 0 et on a donc bien :
EMBED Equation.3
Nous avons ainsi montré Rob(S3) ( min(Rob(S1), Rob(S2)).
Preuve de AGR3 : Ajout de contrat
Nous voulons démontrer que lajout dun contrat dans un système S ne fait pas décroître sa robustesse globale. Si on note S le système après lajout dun contrat, on veut montrer :
Rob(S) ( Rob(S).
Lajout dun contrat dans un composant Ci modifie ni sa robustesse isolée, ni la robustesse locale de ses composants serveurs. Par exemple sur la REF _Ref38686578 \h Figure 40, lajout dun contrat dans C1 modifie la robustesse isolée de C1 ainsi que RobLoc(C2), RobLoc(C3), et la robustesse locale de tous ses serveurs.
Figure SEQ Figure \* ARABIC 40 Ajout dun contrat dans C1
Considérons lajout dun contrat dans Ci, nous notons Ci le composant obtenu après ajout du contrat. La robustesse isolée de Ci est supérieure ou égale à celle de Ci :
Det(Ci, Ci) ( Det(Ci, Ci) (1)
Par ailleurs, la probabilité que Ci détecte une erreur dans un de ces serveurs est supérieure ou égale à celle que Ci détecte lerreur : ( k / Ci RTD Ck, on a Det(Ci, Ck) ( Det(Ci, Ck), donc :
EMBED Equation.3
i.e. :( k / Ci RTD Ck, on a FaibLoc(Ck ,S) ( FaibLoc(Ck ,S) (2)
Si on désigne par C1 le composant modifié, et quon désigne ces serveurs par C2
Cm, avec m < |S|, alors Rob(S) sécrit de la manière suivante :
EMBED Equation.3
Or, pour i > m, FaibLoc(Ci, S) = FaibLoc(Ci, S) car lajout de contrat na aucune influence sur la robustesse des composants dindice i > m qui ne sont pas connectés au composant modifié. De plus, comme le nombre de composants dans S ne change pas, on a |S| = |S| et donc ProbErr(Ci ,S) = ProbErr(Ci ,S). La robustesse Rob(S) se réécrit :
EMBED Equation.3
Comme C1RTDC2
C1RTDCm, linégalité (2) nous dit que :
( k ( [2
m], FaibLoc(Ck ,S) ( FaibLoc(Ck ,S).
De plus (1) nous dit que :
Det(C1, C1) ( Det(C1, C1), i.e. FaibLoc(C1 ,S) ( FaibLoc(C1 ,S).
On a alors :
EMBED Equation.3 ,
et donc :
EMBED Equation.3
Finalement :
EMBED Equation.3
cest-à-dire : Rob(S) ( Rob(S).
Expériences pour paramétrer le modèle
Différentes expériences sur un système orienté objets, nous ont permis de fixer des valeurs pour les différents paramètres du modèle. Nous commençons par mesurer la robustesse isolée des différentes classes du système en utilisant lanalyse de mutation. Puis, toujours à base de mutation, nous évaluons la probabilité Det(Ci, Cj) (probabilité de détection derreurs dun composant j par un de ses clients i).
Estimation de lefficacité des contrats (robustesse isolée)
Figure SEQ Figure \* ARABIC 41 Exemples pour le calcul de la robustesse isolée
Pour calculer la qualité des contrats des classes dun système, cest-à-dire la robustesse isolée dune classe, nous avons utilisé lanalyse de mutation deux fois. Au cours de ces deux analyses, nous utilisons deux techniques différentes pour loracle : la différence de comportements et les contrats (cf. section REF _Ref37931154 \r \h 3.1.1). La première analyse permet de générer un ensemble de cas de test efficaces dont on sait quils tuent 100% des mutants. La seconde permet dévaluer lefficacité des contrats avec les cas de test efficaces. Remarquons que le score de mutation obtenu avec cette seconde analyse est bien une estimation de la qualité des contrats. En effet, puisque cette analyse utilise un ensemble de cas de test efficaces à 100%, un score de mutation plus faible en utilisant les contrats comme oracle nous assure quun mutant ne peut être vivant quà cause de contrats trop faibles. Donc, moins les contrats sont efficaces, plus il reste de mutants vivants. Le nouveau score de mutation, avec les contrats comme oracle, est donc un estimateur valide de lefficacité des contrats.
Au cours de la première analyse, nous avons utilisé loracle par différence de comportement entre la classe initiale et le mutant. Cette analyse se fait de manière incrémentale. Un premier score de mutation est calculé sur un ensemble de cas de test initial. Si ce score nest pas satisfaisant, des cas de tests supplémentaires sont générés pour laméliorer. A la fin de cette première analyse, nous obtenons un ensemble cas de test avec un bon score de mutation pour une classe (>95%).
La seconde analyse est exécutée en nutilisant que les contrats comme fonction doracle, et en utilisant lensemble de cas de test final obtenu lors de la première analyse. Le score de mutation obtenu lors de cette analyse correspond à la proportion de mutants que les contrats peuvent détecter. Ce score est utilisé comme une estimation de la robustesse isolée dun composant. De la même façon quau cours de la première analyse, si cette robustesse initiale nest pas satisfaisante, les contrats sont améliorés.
Par exemple, la REF _Ref35403632 \h Figure 41 montre la méthode transfer de la classe BankAccount, avec ces pré et post conditions, ainsi que cinq mutants pour la méthode (en tout, 12 mutants on été générés). Cette méthode transfère la somme sum du compte courant au compte account. La postcondition peut tuer les mutants 1 et 3 et linvariant tue le mutant 5 avec lappel de méthode suivant : transfer(balance-overdraft, bAcc1). Le score de mutation est de 60% dans ce cas particulier. Pour obtenir la robustesse isolée pour la classe BankAccount, il est nécessaire de générer les mutants pour toutes les méthodes de la classe et calculer le score global.
Calcul de Det(Ci, Cj)
Une fois obtenue la robustesse isolée pour chaque classe, on peut calculer la probabilité Det(Ci, Cj). Cette valeur, pour un composant Ci, est mesurée en injectant des fautes dans les fournisseurs de Ci (les composants utilisés par Ci), puis en exécutant les cas de test de Ci en utilisant les fournisseurs mutés. La proportion de mutants tués correspond à Det(Ci, Cj). Par exemple, les cas de test de Bank peuvent être exécutés avec les mutants de BankAccount, et dans la suite nous illustrons le cas dune erreur issue de la méthode transfer() du fournisseur BankAccount et détectée par les contrats de Bank. Considérons les mutants pour la classe BankAccount donnés sur la REF _Ref35403632 \h Figure 41 et la méthode transferInterest() de la classe Bank ( REF _Ref35406123 \h Figure 42), et considérons un cas de test qui appelle transferInterest() et pour lequel la méthode transfer() est appelée avec une valeur pour lintérêt de accountFrom.balance-accountFrom-overdraft. Si on ignore linvariant de BankAccount, mutant5 nest pas tué par la postcondition de transfer() mais est tué par la postcondition de transferInterest(). Avec cette donnée de test particulière, lattribut balance est supérieur ou égal à overdraft-1 après le transfert du mutant5. La postcondition de la méthode transfer() ne vérifie pas que balance est supérieure ou égale à overdraft, donc lerreur nest pas détectée localement. Par contre, la postcondition de lappelant transferInterest() de Bank vérifie accountFrom.getBalance() ( accountFrom.getOverdraft() et détecte ainsi lerreur introduite dans mutant5.
Figure SEQ Figure \* ARABIC 42 Exemple pour le calcul de Det(Ci, Cj)
Les paramètres du modèle
On peut maintenant définir les différents paramètres du modèle en terme danalyse de mutation.
RobIsolée(Ci) correspond au score de mutation obtenu lors dune analyse de mutation utilisant les contrats comme oracle
Det(Ci, Cj)= proportion de mutants de Cj détectés par Ci, Cj étant un serveur de Ci.
ProbErr(Ci, S) = 1/|S|, |S| étant le nombre de composants/classes dans le système.
Nous pouvons ainsi mesurer, de manière expérimentale, des valeurs pour ces différents paramètres, ce qui nous permet de calculer les valeurs de la robustesse locale et de la robustesse globale. Nous rappelons lexpression de RobLoc(Ci, S) (1-FaibLoc(Ci, S)), ainsi que la robustesse globale :
EMBED Equation.3
EMBED Equation.3
La section suivante décrit une étude de cas qui permet de calibrer les valeurs des paramètres.
Etude de cas
A partir dun système pour lequel chaque classe a un ensemble de cas tests, les buts de létude sont les suivants :
améliorer lefficacité des contrats en utilisant cette approche
évaluer la capacité dun composant à détecter des erreurs dues à ces fournisseurs.
Figure SEQ Figure \* ARABIC 43 Bibliothèque Pylon pour le langage Eiffel
Une étude de cas a été exécutée sur la bibliothèque Pylon pour le langage Eiffel. Cette bibliothèque implante plusieurs structures de données telles que la liste, le dictionnaire
La REF _Ref22456299 \h Figure 43 montre le diagramme de classes de cette librairie qui est composé de 50 classes et 134 relations entre ces classes. La complexité de cette librairie est suffisante pour illustrer notre approche et offrir des résultats intéressants. Nous avons utilisé loutil (Slayer, dédié au langage Eiffel, pour lanalyse de mutation. Cet outil injecte des fautes dans une classe (ou un ensemble de classes) Eiffel sous test, exécute les tests sur chaque mutant et détermine lesquels sont tués par les tests. Cet outil permet davoir une approche incrémentale lors de la génération des cas de test (il permet de conduire une nouvelle analyse uniquement sur les mutants encore vivants) et peut être paramétré. Lutilisateur choisit le nombre de mutants quil veut générer, ainsi que le type derreurs quil veut injecter.
Le REF _Ref19179368 \h Tableau 4 donne la qualité des contrats initiaux et les scores obtenus après amélioration. Il apparaît que la robustesse isolée des classes a été améliorée de manière significative (la meilleure amélioration a permis de passer de 25% à 100%). A la fin du processus damélioration des contrats, un composant contractualisé a une meilleure capacité à détecter des erreurs (entre 75% et 100% dans notre cas). De plus, cette approche permet de détecter les endroits dans le composant pour lesquels les contrats sont trop faibles.
La qualité des contrats correspond à la capacité de ceux-ci à détecter des erreurs. La généralisation de cette approche serait de pouvoir écrire des contrats suffisamment efficaces pour les utiliser comme oracle pour le test (et éviter lécriture doracles explicites pour chaque cas de test). Cependant, les résultats montrent la limite des contrats pour le test, puisquon na pas réussi à tuer tous les mutants avec les contrats. Les plupart des erreurs qui ne sont pas détectées par les contrats affectent létat global du composant. Il est donc difficile, pour des contrats locaux à la fin ou au début dune routine, de détecter ces erreurs. Par exemple, il est difficile décrire des contrats pour une méthode retrait dune classe pile qui vérifie que lélément retiré a bien été inséré préalablement. Les invariants de classe sont mieux adaptés pour la vérification de propriétés globales.
MinimumMaximumMoyen% mutants tués (contrats initiaux)17%83%58,5%% mutants tués après amélioration des contrats72%100%87,5%Tableau SEQ Tableau \* ARABIC 4 Résultats principaux pour l'amélioration des contrats
Figure SEQ Figure \* ARABIC 44 Extraits de la bibliothèque Pylon
Pour mesurer les valeurs de Det(Ci, Cj), les mutants pour tous les serveurs dune classe Cj sont générés, et les scores de mutation pour les cas de test de Ci sur lensemble des mutants concernant les méthodes utilisées par Ci sont calculés. Sur lexemple de la REF _Ref23311434 \h Figure 44, linked_list utilise linked_node et la classe linked_list appelle toutes les méthodes de linked_node. Le calcul de Det(linked_list, linked_node) consiste à générer tous les mutants pour linked_node et à exécuter les cas de test de linked_list. Le score de mutation obtenu correspond à la proportion de mutants de linked_node détectés par les contrats de linked_list. Ce score est la valeur Det(linked_list, linked_node).
Min.Max.Moyen% mutants du serveur tués par le client50%84%69%Tableau SEQ Tableau \* ARABIC 5 Mesures de Det(Ci, Cj)
Pour les mesures de Det(Ci, Cj), toutes les classes du système sont exécutées avec leur contrats optimisés (la valeur moyenne pour la robustesse isolée est de 87%), et les résultats sont présentés dans le REF _Ref19181890 \h Tableau 5.
Application de la mesure sur trois systèmes réels et résultats
Pour illustrer lintérêt de la conception par contrats pour lamélioration de la robustesse, nous lavons appliquée a posteriori à trois études de cas.
Un système de commutateur pour les télécommunications (SMDS : Switched MegaBits Data Service).
La bibliothèque graphique InterViews composée de 146 classes et 420 relations ADDIN EN.CITE Kung199690Kung, D. C.Gao, J.Hsia, P.Toyashima, Y.Chen, C.1996On Regression Testing of Object-Oriented ProgramsThe Journal of Systems and Software32121 - 40January[Kung''96]
La bibliothèque Pylon
Pour ces trois systèmes, nous estimons lévolution de la robustesse globale du système en fonction de lévolution de la robustesse isolée des composants. Sur la REF _Ref22523052 \h Figure 45, nous avons considéré que la probabilité Det(Ci, Cj) est proportionnelle à la robustesse isolée du composant i (i.e. Det(Ci, Cj) = K*RobIsolée(Ci)). En utilisant les résultats du REF _Ref19179368 \h Tableau 4 et du REF _Ref19181890 \h Tableau 5, nous avons fixé le coefficient de proportionnalité à 0,8. De plus, pour lévolution de la robustesse des systèmes sur la REF _Ref22523052 \h Figure 45, nous avons fait lhypothèse que les dépendances dues à lhéritage nont pas deffet sur la robustesse globale.
Les résultats montrent que sil ny a aucun contrat dans le système, la robustesse est nulle, mais que le simple ajout de contrats, même defficacité faible, améliore la robustesse globale rapidement. En effet, pour une valeur robustesse isolée comprise entre 0 et 0,2, la robustesse globale obtenue augmente très rapidement. Pour des contrats de qualité moyenne (robustesse isolée entre 0,2 et 0,6), la robustesse globale augmente proportionnellement à la robustesse isolée. Pour des bons contrats (robustesse isolée entre 0,6 et à 0,85) la robustesse globale augmente moins vite, mais tout de même de manière significative (elle passe de 0,78 à 0,94). Enfin, on observe que pour les trois courbes, faire passer la robustesse isolée de 0,85 à 1, ce qui correspond aux transformations les plus coûteuses, nest pas très intéressant en terme damélioration de la robustesse. Par exemple, la robustesse globale dInterViews est déjà de 0,94 quand la robustesse isolée des composants est égale à 0,85.
Figure SEQ Figure \* ARABIC 45 Évolution de la robustesse globale en fonction de la robustesse locale des composants
Les différents résultats entre les systèmes correspondent à différentes densités dans les relations de dépendance. Comme on la remarqué plus tôt la robustesse locale dun composant peut être améliorée par les contrats de ses clients, et lamélioration de la robustesse locale participe à lamélioration de la robustesse globale. Donc, plus il y a de relations entre les composants dun système, plus la robustesse locale des composants peut être améliorée et plus la robustesse globale peut augmenter.
La REF _Ref36373652 \h Figure 46 montre différentes évolutions de la robustesse globale du système SMDS en considérant différentes valeurs pour limpact des relations dhéritage. La première courbe correspond à lhypothèse pessimiste selon laquelle les dépendance liées à lhéritage ne doivent pas être prises en compte. Pour la seconde courbe (coeff. K=0,2 pour le calcul de Det(Ci, Cj)), ces dépendances sont prises en compte mais de manière moins importante que les autres dépendances. Enfin, nous considérons un troisième cas, où les dépendances dhéritage sont prises en compte de la même manière que les autres. On constate sur la REF _Ref36373652 \h Figure 46 que le rôle de lhéritage est assez marginal sur lévolution de la robustesse.
Tout ce que nous pouvons dire maintenant, cest que les courbes la plus haute et la plus basse sont des bornes pour la véritable courbe dévolution de la robustesse, et que de futurs travaux devraient nous permettre dévaluer de manière plus précise limpact des relations dhéritage pour la robustesse.
Figure SEQ Figure \* ARABIC 46 Évolution de la robustesse de SMDS pour différents coefficients dimpact de lhéritage
Critique du modèle de mesure
Dans ADDIN EN.CITE Briand20021913Briand, LionelLabiche, YvanSun, H.2002Investigating the use of analysis contracts to support fault isolation in object oriented codeInternational Symposium on Software Testing and AnalysisRoma, Italy70 - 80June[Briand''02b], Briand et al. reprennent le modèle que nous proposons pour la robustesse et le généralisent en faisant lhypothèse que tous les composants ont la même robustesse isolée (notée Rob). Les autres hypothèses et définitions sont les mêmes que les nôtres, ils considèrent notamment que Det(Ci, Cj) est proportionnel à la robustesse isolée dun composant (i.e., Det(Ci, Cj) = K(Rob). Ils obtiennent alors lexpression suivante pour la robustesse locale du composant i :
RobLoc(Ci,S) = 1-(1-Rob) + (K(Rob)C, où C est le nombre de clients du composant i. Le nombre moyen de clients pour les composants est ensuite fixé comme une constante dans le système, la robustesse locale est alors la même pour chaque composant. Sous lhypothèse que chaque instruction du système a la même probabilité 1/n dêtre erronée (n étant le nombre dinstruction du système), la robustesse globale sexprime alors ainsi : EMBED Equation.3 .
En se basant sur cette généralisation du modèle, Briand et al. proposent une étude de sensibilité des paramètres C et K, dont les courbes sont données REF _Ref36448799 \h Figure 47 et REF _Ref36448801 \h Figure 48.
La principale critique que Briand émet à propos de notre modèle est que la mesure de robustesse globale est très sensible aux variations des facteurs C et K. Cependant, les courbes montrent que pour des valeurs de K entre 0,5 et 0,8 et de C entre 3 et 6 les variations sont moins importantes. Or notre hypothèse, vérifiée par toutes nos expériences, est que le facteur K se situe généralement aux alentours de 0,8. Quant à la valeur de C, il nous semble tout à fait raisonnable de faire lhypothèse que chaque composant a au minimum trois clients en moyenne (une étude faite dans ADDIN EN.CITE Hanh20021852Hanh, Vu Le2002Test et modèle UML : stratégie, plan et synthèse de testIRISARennesUniversité de Rennes 1[Hanh''02] vérifie cette hypothèse).
En conclusion, même si le modèle est très sensible aux variations de C et K, il reste tout de même valide en pratique, puisque les expériences semblent montrer que ces valeurs ne varient pas énormément, et que, dans cet intervalle, le modèle est moins sensible aux variations des valeurs que dans les intervalles extrêmes.
Figure SEQ Figure \* ARABIC 47 Modèle généralisé par Briand : sensibilité du facteur K pour le calcul de Det(Ci, Cj) (C=3)
Figure SEQ Figure \* ARABIC 48 Modèle généralisé de Briand : sensibilité du nombre de clients (K=0,8)
Une mesure de la diagnosabilité
La diagnosabilité est une mesure qui permet dévaluer la précision et lefficacité du diagnostic ADDIN EN.CITE Jones20021233Jones, James A.Harrold, Mary JeanStasko, John2002Visualization of Test Information to Assist Fault LocalizationICSE'02Orlando, FL, USA467 - 477MayD:\Travail\biblio-bb\diagnostic\icse02(harrold).pdfLe Traon2003160Le Traon, YvesOuabdessalam, FaridRobach, ChantalBaudry, Benoit2003From Diagnosis to Diagnosability: Axiomatization, Measurement and ApplicationJournal of Systems and Software65131 - 50JanuaryD:\Travail\biblio-bb\diagnostic\jssLetraon.pdf[Jones''02; Le Traon''03]. Comme nous lavons vu à la section REF _Ref42515248 \r \h 2.1.1, lorsquune erreur est détectée au cours du test dun logiciel, la phase de diagnostic consiste à localiser la faute pour pouvoir la corriger. Généralement, lerreur nest détectée quà la fin de lexécution dun cas de test, et, pour trouver la faute, il faut alors remonter le flot dexécution du cas de test (lensemble des instructions exécutées par ce cas de test), jusquà trouver linstruction erronée ou la zone suspecte (omission de code
). En présence de contrats, le diagnostic peut être facilité, puisquune erreur peut être détectée par un contrat qui sera alors violé. Il suffit alors de remonter le flot dexécution avant le contrat violé, sans soccuper des instructions après ce contrat.
Lintuition pour lapport des contrats au diagnostic est la suivante : lefficacité du diagnostic pour un système construit par assemblage de composants dépend de la robustesse locale des composants. En effet, plus cette robustesse locale est élevée, plus les contrats détecteront les erreurs tôt dans le flot dexécution, plus lerreur sera proche du contrat violé rendant ainsi le diagnostic plus efficace.
Analyse du problème
Avant danalyser en détail les attributs influençant la diagnosabilité, nous rappelons les méthodes principales pour le diagnostic.
Techniques de diagnostic pour la localisation de fautes logicielles
La première technique largement utilisée pour la localisation de fautes est fondée sur le recoupement dinformations récoltées lors de lexécution dun ensemble de cas de test sur un programme. Le recoupement dinformations récoltées lors du test dynamique permet alors un diagnostic semi-automatique ADDIN EN.CITE Jones20021233Jones, James A.Harrold, Mary JeanStasko, John2002Visualization of Test Information to Assist Fault LocalizationICSE'02Orlando, FL, USA467 - 477MayD:\Travail\biblio-bb\diagnostic\icse02(harrold).pdf[Jones''02]. La plupart des travaux sur le diagnostic sont fondés sur des techniques de « slicing » et se concentrent sur le test unitaire ou dintégration. Il existe plusieurs méthodes pour le « slicing » qui consistent toutes à extraire un ensemble dinstructions qui peuvent être exécutées de manière indépendante (ce qui correspond à un slice) ADDIN EN.CITE Weiser19841190Weiser, M.1984Program SlicingIEEE Transactions on Software Enginnering104352 - 357JulyWeiser19821090Weiser, M.1982Programmers Use Slices When DebuggingCommunication of ACM257446 - 452JulyKamkar19951100Kamkar, M.1995An Overview and Comparative Classification of Program Slicing TechniquesJournal of Systems and Software313197 - 214DecemberKorel19971110Korel, B.1997Computation Of Dynamic Program Slices For Unstructured ProgramsIEEE Transactions on Software Enginnering23117 - 34JanuaryAgrawal19951123Agrawal, H.Horgan, J.London, S.Wong, W.1995Fault Localization using Execution Slices and Dataflow TestsISSRE'95 (Int. Symposium on Software Reliability Engineering)Toulouse, France143 - 151October[Weiser''82; Weiser''84; Agrawal''95; Kamkar''95; Korel''97]. Pour localiser les fautes, il faut alors exécuter les « slices » un par un et analyser le résultat de chaque exécution. Le coût en termes deffort humain est la principale limite de ces techniques. En effet, il faut déterminer une valeur doracle pour chaque « slice » ; or ils nont pas de fonctionnalité clairement identifiée, et loracle nécessite alors une intervention humaine.
Une autre technique pour la localisation des fautes consiste à insérer des assertions dans le programme pour détecter des états erronés au cours de lexécution. Le placement systématique dassertions au début et à la fin de chaque procédure savère une technique efficace pour détecter et localiser des fautes dans un programme. La conception par contrat apparaît ainsi comme une généralisation de ce principe. Cependant, leffort nécessaire à la définition de telles assertions peut être important : cela nécessite une bonne compréhension du fonctionnement interne des procédures ainsi que des valeurs attendues en sortie. Voas dans ADDIN EN.CITE Voas19991060Voas, J. M.Kassab, Lora1999Using Assertions to Make Untestable Software More TestableSoftware Quality Professional14SeptemberZ:\biblio-bb\Testabilité\AssertionsForTestability.pdf[Voas''99] étudie un autre type de placement des assertions fondé sur lobservabilité des variables internes.
Face au problème du diagnostic, qui reste une tâche non entièrement automatisable, il peut être utile davoir une estimation, a priori, de la difficulté pour la localisation des fautes. Ce facteur prédictif est appelé la diagnosabilité (cf. ADDIN EN.CITE Le Traon19981133Le Traon, YvesOuabdessalam, FaridRobach, Chantal1998Software DiagnosabilityISSRE'98 (Int. Symposium on Software Reliability Engineering)Paderborn, Germany257 - 266November[Le Traon''98] pour la conception flot de données) et fournit aussi un facteur de qualité pour lévaluation de la conception dun logiciel.
Dans la suite nous explorons donc limpact de lutilisation de contrats sur la diagnosabilité de programmes OO.
Diagnosabilité : analyse intuitive de la notion
Leffort pour la localisation de fautes est relatif à la taille de lensemble des instructions ou des composant suspects (dans lesquels on estime pouvoir trouver la faute). Plus cet ensemble est grand, plus le diagnostic est difficile. Par ailleurs, lors de la constitution de lensemble des instructions suspectes, on repère des sous-ensemble dinstructions non différentiables du point de vue du diagnostic (rien ne permet de suspecter une instruction plutôt quune autre dans un tel sous-ensemble). En plus de leffort, on associe un critère de précision du diagnostic qui correspond à la taille maximale des sous-ensembles dinstructions non différentiables. Ces diverses considérations permettent dexprimer les trois propositions suivantes pour la diagnosabilité :
la diagnosabilité concerne à la fois leffort de localisation et de précision du diagnostic,
leffort de localisation et la précision du diagnostic sont très fortement corrélés,
la diagnosabilité dépend de la capacité dune technique à isoler les instructions dans le système soit en appliquant une stratégie de test particulière, soit en insérant des assertions ou des contrats. Dans la suite de cette section nous nous concentrons sur les contrats pour isoler les instructions suspectes dans un flot dexécution. Dans les perspectives possibles pour la suite de cette thèse (chapitre REF _Ref36868682 \r \h 6) nous abordons une technique fondée sur le recoupement de traces dexécution de cas de test pour isoler les instructions suspectes.
Lattribut principal pour la localisation des instructions erronées parmi un ensemble dinstructions suspectes est appelé indiscernabilité. Les deux critères pour la diagnosabilité (effort et précision), sont liés au nombre dinstructions non différentiables parmi lesquelles la faute doit être trouvée.
Définitions
A partir des propositions précédentes nous proposons une définition de la mesure de diagnosabilité ainsi que plusieurs hypothèses pour un modèle de mesure qui sera détaillé dans la section suivante.
Diagnosabilité (définition informelle). La diagnosabilité exprime leffort pour la localisation dune erreur ainsi que la précision du diagnostic pour un système précis.
Alors que la mesure de la robustesse se concentrait sur un composant et ses contrats comme une unité dassemblage, la diagnosabilité sintéresse aux instructions exécutées et aux contrats traversés par un cas de test qui échoue. La notion de composant est donc mise en arrière-plan ici au profit de la notion de flot dexécution.
Flot dexécution. Un flot dexécution est un ensemble dinstructions et de contrats partiellement ordonnés qui correspondent à une exécution particulière dun programme.
Les instructions dans un flot dexécution sont partiellement ordonnées puisquune partie de lexécution peut être distribuée sur plusieurs processus (cf. REF _Ref32896803 \h Figure 35). Dans la suite, pour simplifier le modèle mathématique, nous ne considérons que des flots non distribués. Le modèle complet nest pas présenté car de tels modèles de flot ne modifient pas les résultats sur la mesure de manière significative, alors quils rendent le modèle mathématique très complexe.
Instructions non différentiables. Deux instructions sont non différentiables lune de lautre si elles sont entourées de deux contrats consécutifs dans un flot dexécution.
Ensemble indiscernable. Un ensemble indiscernable correspond à un ensemble dinstructions non différentiables.
Dans les définitions suivantes, nous distinguons des mesures de diagnosabilité locales (au niveau dune instruction ou dun flot dexécution) qui sont plus directement liées à leffort de localisation, et une mesure de diagnosabilité globale (pour un système) liée à la précision du diagnostic.
Effort local de diagnostic (dð)ð. La diagnosabilité locale dð d une instruction Inst dans un flot d exécution F est l effort probable pour déterminer si Inst est erronée dans F connaissant que le nombre d instructions, le nombre et la distribution des contrats et leur efficacité.
L effort pour le diagnostic local dépend de lefficacité des contrats qui correspond à la robustesse locale dun composant, et du nombre dinstructions suspectes parmi lesquelles la faute doit être localisée.
Effort local de diagnostic pour un flot. Leffort local de localisation dune faute dans un flot F est leffort probable nécessaire à la localisation de linstruction erronée sachant quune faute a été détectée dans F et connaissant le nombre dinstructions, le nombre de contrats et leur efficacité.
Diagnosabilité globale dun système. La diagnosabilité globale dun système S et le degré probable de précision du diagnostic relatif à la densité et la précision des contrats dans S.
Le modèle est fondé sur les hypothèses suivantes :
le programme est supposé contenir au moins une faute : il existe donc une exécution du système qui provoque une défaillance si lerreur nest pas détectée par un contrat
les contrats sont supposés corrects
si un flot dexécution contient plusieurs fautes, le diagnostic révèlera le premier point de divergence du flot avec le flot attendu (nous considérons que les fautes qui se compensent mutuellement sont négligeables)
Plusieurs paramètres pour la mesure ont été identifiés dans ces définitions (robustesse, nombre dinstructions non différentiables, flot dexécution). La section suivante propose des définitions formelles de ces paramètres, ainsi que de la diagnosabilité à partir de ces paramètres.
La mesure de diagnosabilité
A partir de différents attributs identifiés précédemment, il est possible dénoncer un modèle pour la diagnosabilité. Une fois ce modèle présenté, nous donnons quelques résultats expérimentaux sur le paramétrage des différents attributs, et sur lévolution de la diagnosabilité dun système en fonction de ces attributs.
Mesure locale de la diagnosabilité
Le premier attribut que nous déterminons pour le calcul de la diagnosabilité dun système correspond à la probabilité quune instruction erronée soit détectée par un contrat. Puisque nous ne distinguons pas les instructions à lintérieur dun ensemble indiscernable, cette probabilité est la même pour tout instruction dans un tel ensemble. Cette probabilité dépend du nombre de contrats le long du flot dexécution, que nous appelons #contrats.
EMBED Equation.2 . EMBED Equation.2 , tel que i ([1
#contrats] et j ( [i
#contrats], désigne la probabilité quune instruction erronée dans un ensemble indiscernable EIi soit détectée par un contrat j, sachant quaucun contrat intermédiaire sur le flot na détecté derreur.
La probabilité EMBED Equation.2 est contrainte de la manière suivante : EMBED Equation.3 . Ici, Pdéf désigne la probabilité de défaillance lorsque aucun contrat na détecté lerreur et que la défaillance se propage jusquà la sortie du système. Soit pk la probabilité que le kème contrat détecte lerreur présente dans EIi : puisque pour un contrat j, les non-détections de lerreur par chaque contrat précédent dans le flot dexécution sont des évènements indépendants, on a : EMBED Equation.3 .
Le second attribut auquel nous intéressons correspond à la zone de diagnostic, i.e. au nombre dinstructions exécutées entre linstruction erronée et lendroit où lerreur est détectée. Lorsque des contrats sont présents dans le programme, la zone de diagnostic pour chaque instruction dans un ensemble indiscernable est approximativement la même.
Effort local de diagnostic ((). Pour un flot dexécution particulier, leffort local pour le diagnostic pour un ensemble indiscernable est égal à la zone de diagnostic.
Il reste maintenant à estimer la zone de diagnostic. Si la faute est détectée par un contrat j, alors la zone de diagnostic en fonction des ensembles indiscernables EIi est égale au nombre dinstructions entre linstruction erronée dans EIj et le contrat qui détecte lerreur, soit : EMBED Equation.3 .
Effort de diagnostic (i. Leffort de diagnostic pour une instruction dans un ensemble indiscernable EIi est :
EMBED Equation.3 , où la probabilité Pdéf sobtient de la manière suivante : EMBED Equation.3 .
Le dernier terme dans le calcul de (i est ajouté pour mesurer la zone de diagnostic lorsque aucun ne détecte linstruction erronée (lerreur se propage jusquà la sortie du système, provoquant ainsi une défaillance). Leffort de diagnostic pour un flot correspond à la zone de diagnostic probable connaissant la probabilités Perri que lerreur soit dans EIi. On obtient donc leffort de diagnostic comme suit : EMBED Equation.3
Les différentes formules énoncées ci-dessus pour le calcul de leffort de diagnostic sont trop générales pour une application pratique de la mesure pour un flot. Nous définissons donc les hypothèses suivantes qui permettent de simplifier considérablement le modèle :
les contrats sont répartis uniformément dans un flot dexécution. Chaque ensemble indiscernable a donc la même taille tailleEI qui est égale au nombre dinstructions divisé par le nombre de contrats sur le flot (hypothèse justifiée plus loin)
plus un contrat est proche de linstruction erronée, plus la probabilité quil a de détecter la faute est grande (i.e. pk décroît lorsque k augmente, k ( [i
#contrats])
les contrats ont tous la même probabilité de détecter une faute dans une instruction les précédant immédiatement
chaque instruction a une probabilité uniforme dêtre erronée égale à 1/#instr.
Des expériences ont été réalisées pour justifier la validité de la première hypothèse. Comme nous avons réalisé ces expériences sur des programmes écrits en Java, ne contenant pas tous des contrats, la répartition des contrats est estimée avec la répartition des appels de méthode sur un flot. En effet, si nous faisons lhypothèse quil y a un contrat au début et à la fin de chaque méthode (ce sera vrai sil y a au moins un invariant pour la classe), alors la répartition des appels de méthode correspond effectivement à une approximation de la répartition des contrats dans un flot. Nous avons mesuré cette répartition sur cinq systèmes de taille différentes. La taille des flots dexécution va de 1400 instructions à presque deux millions. Le REF _Ref33509676 \h Tableau 6 détaille les caractéristiques de chaque système. Il apparaît que les contrats sont uniformément répartis dans ces systèmes et que la première hypothèse est valide et peut donc être prise en compte pour le modèle de la diagnosabilité. Le détail des courbes pour ces mesures est donné en REF _Ref43964619 \r \h Annexe A.
Tableau SEQ Tableau \* ARABIC 6 Répartition des contrats
#instructions#contrats#instructions / #contratsclasse List 13915052,75Serveur de réunions virtuelles229111711,96Auto-Test pour JUnit 19419108011,8JDK111730407512,74JTree19707458850012,22Nous avons développé un outil spécifique pour obtenir ces données sur la répartition des contrats dans un flot dexécution. Cet outil est baptisé JTracor, et permet de tracer lexécution dun programme JAVA en utilisant les facilités quoffre linterface JDI ADDIN EN.CITE SUN200219216SUN2002JavaTM Platform Debugger Architecturehttp://java.sun.com/products/jpda[SUN''02] (Java Debugging Interface). Cette interface offre un système de requêtes sur les évènements de la machine virtuelle (appel de méthode, instruction exécutée
), et permet ainsi de tracer lexécution dun programme sans instrumenter le code.
La seconde hypothèse correspond à lintuition et a été vérifiée expérimentalement dans la section précédente. La troisième hypothèse (ainsi que la première) permet de proposer une mesure simplifiée qui évalue limpact des contrats pour la diminution de leffort de diagnostic. Lefficacité de chaque contrat pourrait être mesurée par analyse de mutation comme nous lavons décrit à la section REF _Ref35590259 \r \h 4.2.5, mais ce nest pas lobjet de létude qui cherche à extraire des résultats généraux. Enfin, la quatrième hypothèse est raisonnable dans une approche prédictive puisquelle force à suspecter chaque partie du programme de la même manière.
Pour modéliser la seconde hypothèse et rester cohérent avec la troisième, nous considérons que le premier contrat exécuté immédiatement après linstruction erronée a une probabilité p de détecter lerreur, le second une probabilité (.p, le troisième að2ð.ðp et ainsi de suite. La constante ( est appelée le coefficient d absorption et est compris entre 0 et 1. Lorsque ce coefficient est égal à 1 tous les contrats sont équivalents et la seconde hypothèse ne peut pas être vérifiée, ce qui correspond au modèle le plus optimiste. Si le coefficient est égal à 0, cela signifie que seul le premier contrat peut détecter lerreur.
A partir de ce coefficient, nous pouvons exprimer la probabilité de détection suivante : EMBED Equation.3 . La zone de diagnostic peut se calculer ainsi
ZoneDiagnostic = (#contrats i +1)* tailleEI. Enfin leffort local de diagnostic sexprime ainsi :
EMBED Equation.3 .
Paramétrage du modèle
Lestimation de la qualité des contrats est détaillée à la section REF _Ref35590136 \r \h 4.2.5, et révèle que cette qualité varie de 0,17 à 1 (entre 17% et 100% de détection derreur par les contrats), et quune valeur moyenne raisonnable se situe autour de 0,8.
Il faut ensuite paramétrer le coefficient dabsorption. La REF _Ref33510756 \h Figure 49 décrit une analyse de sensibilité pour ce coefficient. Par exemple, pour des contrats defficacité 0,4, le nombre probable de contrats traversés avant la détection de lerreur se situe entre 3,1 et 6,1 pour dix contrats maximum. On peut aussi observer sur cette figure quune mauvaise estimation du coefficient dabsorption peut induire une perte dinformation lors du calcul de la diagnosabilité globale (en particulier si lefficacité des contrats se situe entre 0,2 et 0,7). cependant, nous considérons que les différences restent acceptables et quelles ne seront jamais en contradiction avec les axiomes.
Les expériences réalisées sur la librairie Pylon ont permis dévaluer le coefficient dabsorption à 0,8.
Figure SEQ Figure \* ARABIC 49 Paramétrage pour 10 contrats
Résultats des mesures de la diagnosabilité globale
Figure SEQ Figure \* ARABIC 50 Résultats pour dð et Dð
Comme nous l avons exprimé plus tôt, l effort de diagnostic pour un flot d exécution est proportionnel à la taille de ce flot, tous les paramètres étant fixés par ailleurs (efficacité et densité des contrats). Ceci peut être démontré facilement à partir du fait quune telle courbe deffort est une fonction homothétique centrée en 1 avec un coefficient multiplicatif égal à la taille du flot. Il apparaît alors que la précision du diagnostic ne dépend pas de la taille du flot, ce qui donne à ce résultat un caractère général.
La REF _Ref33510955 \h Figure 50 représente les même résultats sur deux échelles différentes en termes deffort « absolu » pour le diagnostic et en terme de précision relative. Les courbes pour leffort de diagnostic sont données pour différentes valeurs pour lefficacité des contrats et sur un flot de 1000 instructions, le coefficient dabsorption étant égal à 0,8.
La diagnosabilité globale sobtient donc directement pour nimporte quel flot dexécution, i.e. elle ne dépend que de lefficacité et de la densité des contrats.
Résultats et conclusions
Sur la REF _Ref33510955 \h Figure 50, nous remarquons tout dabord que le simple fait dintroduire des contrats améliore rapidement la diagnosabilité du système. Ensuite, lajout de nombreux contrats (grande densité de contrats) naugmente pas la diagnosabilité de manière significative (qui est bornée par 0,6 dans le cas de contrats avec une efficacité de 0,2 ou par 0,9 dans le cas de contrats avec une efficacité de 0,4). Enfin, la qualité des contrats est plus importante que leur quantité puisque cest le seul moyen daugmenter la borne maximale de la diagnosabilité globale.
Nous pouvons déduire plusieurs choses de ces observations :
une densité de 0,2 (i.e., 2 contrats pour 10 instructions) pour les contrats est suffisante pour atteindre la valeur maximale de la diagnosabilité, la valeur de lefficacité des contrats étant fixée par ailleurs. Remarquons que dans les programmes OO, les méthodes sont souvent petites et quune densité de 0,2 pour les contrats est facilement atteignable dans ce cas. Il est donc inutile de rajouter des assertions à lintérieur des méthodes. Remarquons aussi que ce résultat était difficilement prévisible sans modèle mathématique.
La qualité est plus importante que la quantité puisque, pour une densité fixée, seule la qualité des contrats permet de changer la valeur de la diagnosabilité. Il est donc plus important de fournir un effort pour une bonne conception et des interfaces bien définies (permettant de définir clairement des propriétés sous forme de contrat), plutôt que de fournir un effort sur des assertions défensives, qui sont souvent imprécises et dépendantes du code.
La conception par contrat apparaît donc comme une technique efficace pour lamélioration de la diagnosabilité dun système vu comme un assemblage de composants, et, de manière plus générale, de la qualité globale de ce système.
Conclusion
Au cours de cette étude nous avons pu estimer limpact réel des contrats sur deux facteurs de qualité que sont la robustesse et la diagnosabilité. Ces études, qui mériteraient dêtre complétées, montrent que la simple introduction des contrats améliore de manière importante ces deux facteurs.
Ce travail, a débuté par la présentation dun cadre générique pour la définition dune mesure. Les deux mesures ont ensuite été définies dans ce cadre. Les paramètres significatifs ont été isolés pour chaque mesure, et un modèle a été défini en fonction de ces critères. Des expériences ont permis de fixer les valeurs pour les paramètres, et dobserver le comportement des mesures en fonction de ces valeurs. Les résultats ont montré que la simple introduction de contrats augmente rapidement la robustesse et la diagnosabilité. Dans le cas particulier de la diagnosabilité, au-delà dune densité minimale, la qualité des contrats est le facteur le plus important pour ces deux mesures.
Les résultats pour létude de la mesure de robustesse ont aussi montré la limite des contrats comme fonction doracle pour le test. Cependant, nous pensons quune étude approfondie devrait permettre détablir des règles pour classer les cas de test en fonction de la probabilité quune erreur soit détectée par les contrats en exécutant le cas de test. Il faudrait alors écrire un oracle explicite en priorité pour les cas de test qui ont une faible probabilité de détecter des erreurs avec les contrats.
Anti-patterns de testabilité dans un assemblage de composants
Un principe important de la conception orientée objet consiste à répartir le traitement dune fonctionnalité dans de nombreux objets du système. En conséquence, le contrôle est réparti sur plusieurs objets, et le test doit stimuler, soit explicitement, soit de façon indirecte les interactions entre objets. De plus, puisque les collaborations entre objets sont cruciales, il est également crucial de les détecter tôt et de déterminer la difficulté pour les couvrir lors du test. Au cours de ce chapitre, nous proposons donc un critère de test pour couvrir les interactions entre objets. Celui-ci nous permet ensuite détudier la testabilité ADDIN EN.CITE Binder1994490Binder, R. V.1994Design for Testability in Object-Oriented systemsCommunications of the ACM37987 - 101SeptemberVoas1995110Voas, J. M.Miller, K.1995Software Testability: The New VerificationIEEE Software12317 - 28May[Binder''94; Voas''95] des modèles statiques orientés objet à partir des diagrammes de classes UML(Unified Modelling Language). Cette mesure prédictive permet de détecter les interactions potentielles sur le diagramme et de désigner précisément les zones à améliorer pour diminuer leffort de test (évalué en fonction du critère proposé ici).
Présentation de la problématique
Quand un modèle OO est mal spécifié ou ambigu, des interactions d'objets non souhaitées sont également implicitement spécifiées : toute méthode de génération de test, fondée sur la couverture dinteractions dobjets, produit alors des cas de test inutiles (qui cherchent à couvrir des interactions infaisables). De tels objectifs de test sont conformes à la spécification, mais ne semblent plus raisonnables au moment dêtre appliqués sur l'implantation. Lobjectif de lanalyse de testabilité, au niveau dun assemblage de composants, est dindiquer les parties de cet assemblage où des problèmes dus aux interactions peu testables doivent être examinés. De telles configurations peu testables dans lassemblage sont appelées anti-patterns de testabilité dans cette thèse par analogie avec les anti-patterns de conception répertoriés dans ADDIN EN.CITE Brown19981961Brown, William J. Malveau, Raphael C. Brown, William H. McCormick, Hays W., III Mowbray, Thomas J.1998AntiPatterns: Refactoring Software, Architectures, and Projects in CrisisJohn Wiley & Sons0471197130[Brown''98]. Un anti-pattern décrit une solution à un problème récurrent qui produit des conséquences négatives sur la qualité du logiciel ADDIN EN.CITE Correa2000763Correa, AlexanderWerner, Claudia M. L.Zaverucha, Gerson2000Object Oriented Design Expertise Reuse: An Approach Based on Heuristics, Design Patterns and Anti-patternsInternational Conference on Software Reuse336 - 352June[Correa''00]. Or, les configurations que nous détectons ici sont récurrentes et peuvent dégrader la testabilité de lassemblage de composant.
Pour guider la tâche de test, la principale vue statique dune conception OO, à savoir le diagramme de classes, semble fournir une bonne base pour détecter et maîtriser les dépendances implicites de contrôle réparties dans le système, quelles soient dues à l'héritage ou à la liaison dynamique. Cependant, un diagramme de classes est souvent ambigu, inachevé, et peut mener à plusieurs interprétations fausses, se dérivant en des implantations erronées et, incidemment, à un nombre prohibitif dobjectifs de test cherchant à couvrir ces points dambiguïté. Une analyse de testabilité doit donc permettre, autant que possible, de détecter ces ambiguïtés. On pourrait arguer que les vues complémentaires dUML, telles que les diagrammes de séquence, de collaborations ou d'objets servent précisément à lever certaines ambiguïtés : ceci nest pas vrai pour le test. En effet, elles noffrent que des « instantanés » de configurations valides et ne permettent pas déliminer les comportements non souhaités. Dans la cas du test, le rôle des diagrammes de collaborations se restreint à lexpression des traces qu'un cas de test doit produire ADDIN EN.CITE Abdurazik2000503Abdurazik, A.Offutt, A. J.2000Using UML Collaboration Diagrams for Static Checking and Test GenerationSpringer-VerlagUML'00York, UK383 - 395October[Abdurazik''00], de même que les diagrammes de séquence offrent une base pour indiquer des objectifs nominaux et exceptionnels de test ADDIN EN.CITE Pickin20021683Pickin, SimonJard, ClaudeLe Traon, YvesJéron, T.Jézéquel, Jean-MarcLe Guennec, Alain2002System Test Synthesis from UML Models of Distributed SoftwareFORTEZ:\biblio-bb\TestOO\UMLbased\UML-TGV.pdf[Pickin''02]. Les diagrammes de collaborations et de séquence peuvent aider à comprendre les interactions mais ne peuvent pas détailler chacune dentre elles, ni limiter leur nombre. De la même manière, les diagrammes d'objets représentent seulement une configuration particulière des instances de classes dans le système, mais ne décrivent pas toutes les configurations valides. Seuls les diagrammes détats représentent exhaustivement un comportement dynamique donné, mais on ne dispose pas nécessairement de cette vue pour toutes les classes du système. De même, leur combinaison du fait de la création dynamique dobjets et de la communication asynchrone entre objets produit des automates infinis ou de taille telle quon ne peut pas toujours les traiter. En conséquence, nous considérons que les vues principales sur lesquelles la testabilité doit être analysée sont les diagrammes de classes et détats, et que les autres vues montrent seulement des instantanés de quelques comportements possibles. Létude décrite dans ce chapitre se concentre sur les diagrammes de classes UML.
Les problèmes de testabilité dans un diagramme de classes sont dus à l'existence de relations client/serveur dans le système, tout comme pour n'importe quel logiciel classique. En effet, s'il n'y a aucun client dans le logiciel, il n'y a pas non plus densemble défini d'exécutions, et donc il ny a rien à tester. Aussi, après le test unitaire, les défaillances devraient se produire seulement en raison dinteractions erronées entre objets : ces interactions traversent toute l'architecture et sont dautant plus complexes lorsque les dépendances client/serveur traversent des arbres d'héritage. Les dépendances polymorphes multiplient le nombre potentiel de types d'objet qui peuvent agir les uns sur les autres avec diverses et peut-être fausses implémentations. Un critère de test qui exige la couverture explicite de ces interactions d'objets doit alors être défini. Pour être applicable, le nombre de cas de test doit rester raisonnable et nous proposons une évaluation de l'effort de test, mesuré en estimant le nombre d'interactions d'objets à partir du diagramme de classes d'UML. L'évaluation que nous considérons comme significative de la testabilité globale d'un diagramme de classes est donc le nombre « d'interactions de classes ». Une interaction de classes est une configuration topologique dans le diagramme de classes que les tests doivent couvrir. Elle se produit si une classe est fournisseur dautres classes par divers chemins possibles de dépendances.
A partir du critère de test proposé, notre objectif est triple :
Fournir un modèle qui capture les interactions de classes et indique exactement les causes de ces interactions. On appelle ces configurations recouvertes dans le diagramme de classes des anti-patterns de testabilité. Dans la suite nous en définissons deux.
Mesurer le nombre et la complexité (due au polymorphisme) des anti-patterns. Cette mesure est considérée ici comme notre évaluation de testabilité dun modèle.
Suggérer des améliorations au modèle pour réduire le nombre et la complexité des anti-patterns de classes : ces améliorations au niveau du modèle sont réalistes puisque les vérifications statiques sur le code assurent leur exécution.
Quand ces objectifs sont atteints, nous pouvons rejeter ou accepter un modèle selon un critère de testabilité. On peut alors choisir entre rejeter le modèle si aucune amélioration ne peut limiter les anti-patterns, ou bien passer outre, et accepter de ne pouvoir couvrir toutes les interactions. Le modèle doit être assez précis pour repérer exhaustivement les interactions, en restant assez simple, et proche du contexte réel, pour aider à résoudre les problèmes de testabilité.
Une fois que le modèle et le critère de test sont définis pour un diagramme de classes, nous étudions la testabilité des design patterns ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95]. Deux raisons nous ont conduits a à étudier les design patterns dans le contexte de la testabilité. D'abord, nous montrons que l'application de patrons aide à résoudre des problèmes classiques de testabilité en diminuant la taille des méthodes et en supprimant les structures explicites de contrôle. L'inconvénient est que de nombreuses classes et relations supplémentaires sont introduites, conduisant à des interactions entre ces classes. L'application de design patterns semble donc simplifier la structure du code, au prix dun modèle plus complexe. Nous appliquons ainsi notre analyse de testabilité sur des applications de design patterns et nous proposons des solutions pour résoudre les problèmes qui sont soulevés à la fin de l'analyse.
La deuxième raison pour laquelle les design patterns sont étudiés ici, est qu'ils offrent une décomposition de grands diagrammes de classes en sous-ensembles cohérents. Certaines méthodologies aident à établir une architecture OO en raffinant un premier diagramme de classes par lapplication des design patterns. Ces étapes d'amélioration peuvent être vues comme la cristallisation des microarchitectures autour d'un premier diagramme de classes central. En utilisant une telle méthodologie, les problèmes de testabilité peuvent être plus facilement identifiés et résolus au niveau du sous-ensemble.
La section REF _Ref43735277 \r \h 5.3 présente tout dabord notre critère de test pour couvrir les interactions de classes. Nous y présentons également de manière informelle deux anti-patterns qui peuvent mener à des modèles difficiles à tester. La section REF _Ref43735349 \r \h 5.4 présente un modèle de graphe qui fournit une abstraction d'un diagramme de classes appropriée à l'analyse de testabilité. Enfin, la section REF _Ref43735402 \r \h 5.5 donne des indices pour préciser le modèle afin de diminuer le nombre d'interactions de classes. Les sections REF _Ref43735513 \r \h 5.7 et REF _Ref43735553 \r \h 5.8 traitent exclusivement des architectures fondées sur des design patterns.
Testabilité dune architecture OO: définitions et méthodologie
Cette section présente le contexte général pour notre étude de la testabilité des diagrammes de classes UML. Nous revenons tout dabord sur lapproche globale pour la construction dune mesure de testabilité dans le cadre particulier de létude présentée ici. A partir de cette mesure, il est alors possible de définir une méthodologie pour la conception de systèmes testables. Enfin, nous présentons un exemple simple qui sera utilisé pour illustrer certaines notions au cours de cette étude sur la testabilité.
Testabilité
Le facteur de testabilité apparaît tout dabord comme abstrait et difficile à estimer. En effet, comment évaluer la facilité pour tester un logiciel ? La solution fréquemment employée consiste à considérer un autre facteur plus facilement quantifiable comme un bon indicateur de la testabilité. Comme nous lavons abordé à la section REF _Ref38794486 \r \h 2.6, au cours de ces travaux nous nous concentrons sur leffort de test.
Nous avons eu une approche pragmatique de cette mesure, contrairement à létude sur la robustesse et la diagnosabilité (chapitre REF _Ref35065295 \r \h 4), où nous proposions une axiomatisation de la mesure, conformément aux approches top-down classiques pour lélaboration de mesures. Dans le cas de létude de la testabilité des diagrammes de classes, nous avons dabord étudié précisément des applications concrètes pour identifier les attributs significatifs de la testabilité. Le travail préliminaire à cette étude consiste à définir précisément le critère de test que nous cherchons à satisfaire, et à être capable dévaluer leffort nécessaire pour tester un programme en fonction de ce critère.
Le critère que nous avons retenu comme révélateur de la faiblesse dune architecture pour la testabilité, est la notion danti-pattern de testabilité. De la même manière que les design patterns correspondent à de bonnes solutions de conception dans un cadre particulier, les anti-patterns correspondent à de mauvaises solutions, qui peuvent endommager la qualité dune conception pour un critère particulier. Nous identifions deux anti-patterns pour la testabilité, qui correspondent à des configurations dans le diagramme de classes qui peuvent entraîner une implantation difficile à tester. Dans la suite de ce chapitre nous les définissons précisément, ce qui nous permet dexpliciter le critère de test que nous cherchons à vérifier. Une mesure de complexité est ensuite associée à ce critère de test, qui correspond à lestimation de leffort pour vérifier le critère, et donc à lestimation du coût de test.
Une méthodologie pour la conception de systèmes testables
La REF _Ref27886603 \h
Figure 51 présente lorganigramme de la méthodologie que nous préconisons pour améliorer la testabilité dune architecture, applicable dans un cadre OO. La première étape de cette méthode consiste à faire une analyse de testabilité pour un diagramme de classes. Cette analyse permet de repérer des points dans la conception qui sont la cause dune faiblesse de larchitecture du point de vue de la testabilité. Comme cela est décrit dans la section REF _Ref43735277 \r \h 5.3, ces points correspondent à des configurations particulières dans le diagramme de classes qui peuvent conduire à des implantations difficiles à tester, que nous appelons des anti-patterns de testabilité. Pour pouvoir analyser un diagramme automatiquement, il est nécessaire de le modéliser de manière non ambiguë pour appliquer une analyse des points critiques pour la testabilité.
Figure SEQ Figure \* ARABIC 51 Méthode pour améliorer la testabilité dun logiciel OO
Lanalyse détermine les points du diagramme de classes quil faut améliorer. Cette analyse permet également dobtenir une mesure de la complexité de ces points critiques pour la testabilité (cf. section REF _Ref29104501 \r \h 5.4.3). Une fois cette analyse effectuée, la décision doit être prise entre améliorer ou rejeter le diagramme de classes sil est jugé trop difficile à tester ou bien laccepter et limplanter dans le cas contraire. Une manière daméliorer la testabilité du diagramme peut être de réduire le couplage ADDIN EN.CITE Fowler2001510Fowler, Martin2001Reducing CouplingIEEE Software184102 - 104July-August[Fowler''01] ou dexprimer des contraintes qui aideront le développeur à éviter dimplanter des interactions entre objets difficiles à tester. Dans la section REF _Ref20113048 \r \h 5.4.3 nous suggérons une autre piste, moins coûteuse, qui consiste à utiliser des stéréotypes sur les associations et les dépendances pour spécifier le rôle de ces liens (création, lecture ou écriture). Quand le logiciel est implanté, les contraintes correspondant aux stéréotypes sont vérifiées.
Exemple
Figure SEQ Figure \* ARABIC 52 Diagramme détats pour le gestionnaire de livre
Figure SEQ Figure \* ARABIC 53 Diagramme de classes UML pour un sous-système de gestion dun livre
Nous présentons ici lexemple qui illustre certains points tout au long de ce chapitre sur la testabilité. Cet exemple consiste en un sous-système pour gérer un livre dans une bibliothèque. Le diagramme détats de la REF _Ref27540305 \h Figure 52 décrit le comportement dynamique dun objet livre. Un objet est créé lorsquun livre est commandé (état initial), il peut ensuite être réservé à tout moment. Quand le livre arrive à la bibliothèque, il est soit disponible, soit réservé, et il peut alors être emprunté. Si le livre est endommagé et quil est dans la bibliothèque, il peut être réparé.
La REF _Ref27540156 \h Figure 53 illustre un diagramme de classes possible pour implanter la machine détats. Dans ce diagramme de conception les états et les évènements de la machine détats ont été réifiés en appliquant les design patterns State et Command. Toutes les classes, associations et dépendances sont données, mais, pour une raison de lisibilité, ne sont visibles que les méthodes et attributs qui sont utilisés pour les exemples.
Le contexte général pour létude de testabilité étant établi, la section suivante décrit les types dinteractions qui sont analysées pour estimer la testabilité dun diagramme de classes. Nous proposons ensuite un critère de test pour couvrir ces interactions, et nous illustrons ce point en écrivant les cas de test nécessaires à la vérification du critère pour le sous-système de gestion de livre.
Critère de test et anti-patterns pour les architectures OO
Lintérêt dun critère de test est doffrir un moyen objectif de contrôler larrêt de la génération de cas de test, et un objectif à atteindre pour les testeurs. Dans le cadre des interactions dobjets, il est utile doffrir un critère qui formalise ce que les cas de test doivent couvrir dans un diagramme de classes dun point de vue structurel. Nous proposons donc un critère qui vise à couvrir toutes les interactions entre objets susceptibles dentraîner des effets de bord. Le diagramme de classes UML est la spécification principale utilisée pour définir ce critère. Dans la suite de cette section, nous montrons que, pour appliquer ce critère de manière raisonnable, larchitecture doit être aussi proche que possible de limplantation.
Dans la suite, nous commençons par une description informelle des problèmes de testabilité en étudiant le diagramme de classes de la REF _Ref27540156 \h Figure 53. Ces problèmes correspondent à des configurations particulières qui apparaissent dans un diagramme de classes et qui peuvent entraîner des problèmes pour le test. Ces configurations sont appelées des anti-patterns pour la testabilité, puisquelles correspondent à des configurations récurrentes quil faut éviter pour une meilleure testabilité. Nous formalisons cette notion plus loin dans la section, et montrons aussi comment lhéritage peut augmenter la complexité de ces anti-patterns.
Ensuite, nous décrivons ces anti-patterns plus précisément en termes déléments dans un diagramme de classes UML, ce qui permet de définir un critère de test pour couvrir ces interactions non-désirées. Cette section se termine par un retour sur lexemple de gestion de livre avec lécriture des cas de test pour ce système qui vérifient le critère de test.
Analyse informelle des anti-patterns de testabilité
Nous décrivons ici, de manière informelle, les interactions qui nous intéressent pour évaluer la testabilité dun diagramme de classes. Prenons lexemple du diagramme de la REF _Ref27540156 \h Figure 53, qui correspond à un exemple typique darchitecture OO. En effet, les mécanismes orientés objet de base tels que lhéritage, les classes abstraites, et la délégation sont utilisés. Au premier regard, cette architecture révèle une forte inter-dépendabilité pour le traitement des différentes classes. Par exemple toutes les classes filles dépendent fortement de leurs parents ou encore Book et BookState dépendent lune de lautre. Dautre part, des dépendances indirectes peuvent aussi exister, comme entre BookEvent et Book à travers deux chemins différents : un direct entre les deux classes et un autre via BookState. Du fait de ces relations nombreuses et complexes, il y a un grand risque que des erreurs apparaissent au moment de limplantation de cette architecture (de fait, des erreurs ont effectivement été commises par le programmeur, pourtant expérimenté, qui a implanté ce programme). Les deux sources de problème identifiables sur un diagramme de classes sont les suivantes :
Quand une méthode m1 dans la classe Book utilise une méthode m de la classe BookState, la classe BookState peut utiliser la classe Book pour traiter m. Cela signifie que la classe Book peut s'employer elle-même quand elle utilise BookState.
Quand la classe BookEvent sutilise elle-même, elle peut le faire de deux manières différentes: directement avec Book en paramètre dune de ses méthodes ou bien via BookState qui emploie Book.
Le nombre exact d'interactions, ainsi que leur complexité, est difficile à déterminer par une observation simple du diagramme, cest pourquoi nous avons besoin d'un modèle pour les déterminer toutes de manière exhaustive.
Cette analyse informelle met en évidence deux constructions récurrentes pouvant altérer la testabilité : des interactions d'une classe à l'autre, et une configuration que nous appelons auto utilisation, qui correspond à une classe qui s'emploie elle-même par le biais des dépendances transitives d'utilisation.
Remarque : linteraction dauto-utilisation semble nêtre quun simple « call-back ». Cependant, plusieurs raisons nous ont amené à définir et à utiliser la notion dauto-utilisation plutôt que celle de « call-back » pour désigner lanti-pattern de testabilité, et tout dabord le manque dune définition précise du « call-back » (nimporte quel appel de méthode sur lobjet appelant pour certains, appel de méthodes pour répondre aux évènements sur lIHM pour dautres
). Une seconde raison tient au fait que, comme nous le verrons par la suite, nous distinguons linteraction de classes (et donc potentiel, puisquil est détecté sur un diagramme de classes) et linteraction dobjets (et donc effectif puisquil concerne des relations entre objets du système). Or, un « call-back » ne concerne que des appels de méthodes effectifs entre objets du système. Enfin, la dernière raison, et la plus importante, est linterprétation que nous faisons de cette boucle dappels de méthodes. Le « call-back » insiste sur le fait quun objet qui est appelé par objet distant rappelle cet objet. Or, ce qui nous intéresse dans le cas de lanti-pattern de testabilité, tient plus au fait quun objet qui appelle une méthode peut déclencher un appel de méthode sur lui-même et donc entraîner une modification de son propre état. Dans ce cas, la dénomination auto-utilisation nous semble donc plus appropriée pour désigner lanti-pattern.
Ces interactions peuvent être vues comme des anti-patterns pour la testabilité, par analogie avec les anti-patterns de conception, en ce sens quelles correspondent à des configurations récurrentes sur un diagramme de classes qui peuvent avoir des conséquences négatives sur la testabilité du logiciel.
Anti-pattern pour la Testabilité. Un anti-pattern représente une mauvaise solution pour le développement dun logiciel dans un contexte particulier. Cela correspond à une configuration dans le diagramme de classes qui peut entraîner une augmentation de leffort de test nécessaire à la vérification du critère de test.
La complexité des deux anti-patterns augmente lorsque les relations de dépendance traversent un arbre d'héritage en raison du polymorphisme. La section suivante illustre ce point.
Complexité de l'héritage
La complexité due à l'héritage apparaît quand les dépendances transitives passent par une ou plusieurs hiérarchies d'héritage. Sur la REF _Ref20113104 \h Figure 54, il y a une interaction de classes de c à d. L'interaction est complexe car si c utilise une instance de la classe a ou a2 ou a21, ces trois classes ont toujours des relations potentielles entre elles. En effet, le code de a2 peut dépendre de a comme de a21. Dans ce cas, l'interaction de c avec chacune des trois utilisations potentielles (a ou a2 ou a21) doit être examinée, et pour chacune d'elles, nous devons examiner les relations entre les classes dans la hiérarchie de l'héritage. Cependant, en contraignant le modèle (et en le rendant plus précis), nous pouvons réduire la complexité de l'interaction. En effet, si les classes a et a2 sont des classes d'interface, nous pouvons nous assurer que c ne peut effectivement utiliser que a21 ou a22, les classes parentes noffrant plus aucun code à ces deux classes : la surface d'interaction avec la classe d est ainsi réduite à la classe a21.
Figure SEQ Figure \* ARABIC 54 Interaction de classes à travers une hiérarchie d'héritage
On obtient donc les contraintes suivantes sur les informations que notre modèle doit capturer depuis le diagramme de classes. Le modèle de test devra distinguer les dépendances orientées vers les ancêtres de celles orientées vers les descendants dans un arbre d'héritage. De même, le modèle de test ne doit pas créer de dépendances entre les classes surs, puisqu'elles sont toujours indépendantes du point de vue du test. Enfin, le modèle doit également saisir la complexité de l'interaction.
Critère de test pour des systèmes OO
Nous revenons ici sur les anti-patterns identifiés dans la section REF _Ref43735277 \r \h 5.3 et en donnons une définition plus précise en termes déléments dun diagramme de classes UML. Le critère de test pour couvrir ces anti-patterns lors du test de limplantation est alors défini. Il vise à identifier les erreurs dues à des effets de bord lorsque les relations entre objets sont complexes. Par exemple, si un objet peut modifier létat dun autre de manière indirecte à travers plusieurs chemins de dépendance différents, létat de lobjet modifié peut devenir incohérent vis-à-vis de lobjet client. Dans un tel cas, des test indépendants pour chaque relation ne permettront pas de détecter cette incohérence : il faudra tester cette interaction explicitement avec un unique scénario de test.
Dans un système OO, les classes dépendent les unes des autres pour leur traitement. Une classe a utilise une classe b si des méthodes de la classe a font appel à des méthodes de b, sur un attribut ou une variable locale de type b. Ces relations sont représentées par une association ou une dépendance de a vers b sur le diagramme de classes. On appelle ces relations des relations directes dutilisation.
Relation directe d'utilisation. Il y a une relation directe d'utilisation de la classe a vers la classe b sur un diagramme de classes UML, s'il existe une association ou une dépendance de a à b. Dans le cas d'associations non dirigées, les dépendances existent de a à b et de b à a. L'ensemble des relations dutilisations directes pour un diagramme de classes est noté SDU. La REF _Ref29104060 \h Figure 55 illustre les deux types de relations entre classes : une association entre les classes BookState et Book et une dépendance entre BookEvent et Book.
La notion de relation directe peut être étendue à une relation transitive d'utilisation. En effet, une relation peut exister entre deux classes a et b même s'il n'y a ni association ni dépendance entre elles ; ceci est dû aux relations transitives.
Relation transitive d'utilisation. La fermeture transitive de SDU définit toutes les relations transitives d'utilisation entre les classes du diagramme de classes. On note ( lensemble des relations transitives dutilisation. Si limplantation finale permet l'instanciation d'une relation transitive d'utilisation d'un objet o1 de la classe a avec un d'objet o2 de la classe b, on parle alors dune relation transitive effective de a vers b.
Figure SEQ Figure \* ARABIC 55 Relations directes entre classes
Par exemple, la REF _Ref27538069 \h Figure 56 illustre deux relations entre les classes BookEvent et Book. Une dépendance stéréotypée « uses » entre les deux classes spécifie une relation directe dutilisation. La seconde relation est transitive à travers la classe BookState. La classe BookEvent dépend de BookState qui dépend de Book. La classe BookEvent peut donc dépendre de manière transitive de Book lorsquelle utilise des services de BookState. Cette relation est une relation effective si les méthodes de BookState appelées par BookEvent utilisent des services fournis par Book. Le diagramme de séquence de la REF _Ref27539143 \h Figure 57 illustre une relation transitive effective entre BookEvent et Book. Lorsquun objet de type Borrow (donc de type BookEvent) appelle la méthode borrow() de la classe Ordered, cette méthode appelle la méthode setCurrent_state() de Book. Il apparaît donc quun objet de type Borrow dépend effectivement dun objet de type Book via un objet de type BookState.
Figure SEQ Figure \* ARABIC 56 Relation transitive dutilisation entre BookEvent et Book via BookState
Définissons maintenant les notions d'interaction de classes et d'auto-utilisation. Ces interactions sont potentielles puisqu'elles sont détectées à partir du diagramme de classes qui est une vue abstraite du logiciel. Nous définissons alors la notion dinteraction dobjets qui est une interaction effective puisque les relations entre les objets courants sont impliquées. Certaines d'entre elles peuvent être détectées au niveau du modèle grâce aux diagrammes d'objets ou aux diagrammes de séquence. Cependant, ces diagrammes noffrant quune vue partielle du système, et étant susceptibles de changer, ils ne peuvent pas être employés pour détecter toutes les interactions effectives dans le système. Ces deux notions sont formalisées dans les définitions suivantes.
Figure SEQ Figure \* ARABIC 57 Diagramme de séquence illustrant une relation transitive effective entre BookEvent et Book
Interaction de classes (interaction potentielle). Une interaction de la classe a vers la classe b se produit ssi : ( Ri ( ( et Rj ( (, Ri ( Rj, tel que a Ri b et a Rj b.
Une interaction dauto-utilisation se produit autour de la classe a si a Ri a. La REF _Ref31694132 \h Figure 58 illustre les deux configurations génériques.
Figure SEQ Figure \* ARABIC 58 Interaction de classes et auto-utilisation
La REF _Ref27538069 \h Figure 56 illustre une interaction de classe entre BookEvent et Book. Deux dépendances entre ces classes sont impliquées dans linteraction. Il est à noter que de manière générale, une interaction de classes peut impliquer plus de deux relations transitives d'utilisation.
Interaction d'objets (interaction effective). Il existe une interaction d'objets d'un objet o1 de la classe a à o2 de la classe b ssi : ( Ri ( ( et Rj ( (, Ri ( Rj , tel que a Ri b et a Rj b, ou a Ri a.
Ri et Rj sont des relations transitives effectives pour o1 and o2. Dans le cas où les relations ne sont pas effectives pour les objets, lanti-pattern est infaisable.
Par exemple, si le diagramme de séquence de la REF _Ref27539143 \h Figure 57 est associé au diagramme de classes de la REF _Ref27538069 \h Figure 56, linteraction de classe entre BookEvent et Book est une interaction dobjets.
Propriété. Le nombre d'interactions de classe est une borne supérieure du nombre d'interactions d'objets.
La propriété est évidente si on fait lhypothèse que le code est dérivé (probablement automatiquement à l'aide d'un AGL approprié) du modèle.
Maintenant que nous avons défini les interactions de classe et d'objet, nous pouvons donner notre critère de test.
Critère de test. Pour chaque anti-pattern de testabilité :
soit un cas de test est produit qui exécute une interaction d'objets correspondante,
soit un rapport est produit qui justifie que cet anti-pattern est infaisable.
La génération des cas de test et rapports de test est impossible si le nombre d'interactions de classe est trop élevé. Le but principal de cette étude est la limitation de ces interactions en améliorant le diagramme de classes. En effet, le diagramme doit être aussi proche que possible de limplantation. Même avec le code, les dépendances effectives ne peuvent pas être statiquement déduites, car les langages OO permettent le polymorphisme et la liaison tardive. Puisque le nombre dinteractions de classes est une limite supérieure du nombre d'interactions d'objets, nous recommandons dajouter des informations sur le diagramme de classes. Ces informations devraient permettre de réduire le nombre dinteractions de classes, et donc le nombre dinteractions effectives. Les informations supplémentaires sont des contraintes (par exemple exprimées en utilisant des stéréotypes UML) que le programmeur doit respecter au moment de limplantation et qui peuvent être statiquement vérifiées. Lemploi de la vérification statique au niveau du code permet alors de réduire l'effort de test. Par exemple, si le stéréotype «instantiate» est employé pour une dépendance de a vers b, le code de la classe a devrait appeler seulement les méthodes de création de b. Cette contrainte de conception peut être vérifiée statiquement sur le code.
Exemple pour la génération de test
Cette section illustre la génération de cas de test satisfaisant le critère de test défini précédemment sur lexemple du système de gestion de livres. Deux interactions dauto-utilisation et une interaction de classes sont présentes dans le diagramme de la REF _Ref27540156 \h Figure 53:
AU1 de Book vers elle-même à travers BookEvent
AU2 de Book vers elle-même à travers BookState
CI entre BookEvent et Book par deux chemins différents (un direct et un autre passant à travers BookState).
Le critère de test précise quil faut produire un cas de test pour chaque interaction. Si les interactions ne sont que potentielles, un rapport établissant labsence dinteraction effective doit être établi. Un cas de test consiste donc à créer une instance de Book et à appeler des méthodes sur cet objet.
Test dAU1
Un cas de test couvrant AU1 doit appeler une méthode de Book qui utilise lensemble commands. De plus, cette méthode doit appeler une méthode de BookEvent qui utilise Book. Dans la classe Book, seule la méthode manageEvent() utilise lensemble commands. Dans toutes les classes concrètes implantant les évènements les méthodes sont de la forme suivante :
execute(Book b){...}
Donc toutes les classes dévènements utilisent la classe Book. Comme manageEvent() utilise un objet de type BookEvent, alors un cas de test qui appelle manageEvent() couvre linteraction AU1. Voici un exemple dun tel cas de test (TC1) :
public void testManageEvent(){
Book b = new Book();
b.manageEvent(setDamaged);
}
Test dAU2
Un cas couvrant AU2 devrait appeler une méthode dans la classe Book qui utilise lattribut currentState. En fait, il ny a pas de méthode dans la classe Book qui appelle une méthode sur cet attribut. Linteraction dauto-utilisation AU2 est donc infaisable.
Test de CI
Figure SEQ Figure \* ARABIC 59 Diagramme de séquence pour le cas de test n°3
Le cas de test doit nécessairement appeler une méthode de Book qui utilise lensemble commands, pour atteindre la classe BookEvent, qui est la source de linteraction CI. Puis il faut couvrir le chemin direct de BookEvent à Book et le chemin de BookEvent à Book qui traverse BookState. En écrivant le cas de test TC1, nous avons vu quun appel à la méthode manageEvent()couvre la relation de Book à BookEvent et aussi la relation directe de BookEvent à Book. Le chemin direct de BookEvent à Book est donc couvert par un cas de test qui appelle la méthode manageEvent()de Book.
Pour couvrir le second chemin de BookEvent à Book (à travers BookState) lappel à manageEvent() doit couvrir la relation entre BookEvent et BookState. Pour cela, il faut appeler un événement dont le traitement dépend de létat du livre, dans ce cas la méthode execute() de lévénement est de la forme suivante :
execute(Book b){b.getState();...}
Ensuite, si une transition du diagramme détats est exécutée par lappel dévènement, alors la relation entre BookState et Book est couverte à son tour, puisque dans ce cas la méthode dans létat concret appelle une méthode sur lattribut context. Par exemple, la méthode borrow() dans la classe Available doit changer létat du contexte auquel il est associé, puisque lévénement demprunt dans létat available déclenche une transition vers létat borrowed. Voici le code correspondant :
class Available{
public void borrow(){context.changeState(new Borrowed());}
}
Pour résumer le troisième objectif de test, le cas de test TC2 suivant couvre linteraction CI entre BookEvent et Book, et la REF _Ref27556479 \h Figure 59 donne le diagramme de séquence représentant lexécution de ce cas de test.
public void testManageEvent(){
Book b = new Book(); //the book is in the ordered state
b.manageEvent(deliver); //puts the book in the available state
b.manageEvent(borrow);
}
Le REF _Ref27973721 \h Tableau 7 résume les résultats du test de limplantation du système de gestion de livre ( REF _Ref27540156 \h Figure 53) à partir du critère de test de la section précédente. Ce tableau présente le statut de chaque anti-pattern présent dans le diagramme (faisable ou non), puis quels sont les anti-patterns couverts par chaque cas de test. Il y a en fait beaucoup plus de trois anti-patterns qui devraient être testés à cause des sous-classes de BookState et BookEvent. La section REF _Ref29203350 \r \h 5.4.3 détaille le calcul de la complexité de ces anti-patterns. Cette complexité correspond au nombre maximum danti-patterns qui peuvent apparaître et qui doivent être testés.
Tableau SEQ Tableau \* ARABIC 7 Rapport de test pour le gestionnaire de livres
SU1SU2CIStatutinfaisableTC1XTC2XLoutil JTracor, que nous avons évoqué au chapitre précédent, peut être utilisé pour aider la génération de cas de test satisfaisant le critère de test. En effet, comme loutil produit des traces dexécution pour des programmes écrits en Java, il est possible de connaître les interactions effectives entre objets et quelles méthodes ont été appelées sur ces objets. Les traces obtenues avec JTracor pour lexécution de TC1 et TC3 sont données dans l REF _Ref43966430 \r \h Annexe B.
Modélisation des anti-patterns
Dans les sections précédentes, nous avons exprimé les propriétés quun modèle abstrait du diagramme de classes devrait vérifier pour pouvoir indiquer exactement toutes les interactions de classe pour un système. Le modèle que nous proposons est fondé sur un graphe dont nous donnons ici les règles de dérivation à partir d'un diagramme de classes UML. Un tel graphe s'appelle un Graphe des Dépendances de Classes (GDC). Après avoir défini précisément la construction de ce graphe et quelles informations il comporte, nous donnons des règles sur la topologie du graphe qui déterminent formellement les anti-patterns. Le GDC sert de base pour appliquer des algorithmes de graphe classiques pour détecter les anti-patterns et mesurer leur complexité.
Construction dun graphe à partir d'un diagramme de classes d'UML
Un graphe de dépendances de classes peut être construit automatiquement à partir dun diagramme de classes. Les définitions suivantes précisent quelles informations le GDC doit contenir, et comment les obtenir à partir dun diagramme de classes. L'ensemble de toutes les classes d'un système est désigné par C, et M(c) désigne l'ensemble des méthodes d'une classe c ( C.
Un Graphe De Dépendance De Classes (GDC). Un graphe de dépendance de classe est une paire GDC=(X, (), où :
- X est l'ensemble des sommets, chaque sommet représentant une classe d'un système orienté objet. Une classe est représentée par un simple sommet.
- ( est l'ensemble de paires (x,y) ( X2, appelé ensemble des arcs orientés ((x,y)((y,x)) Un arc entre deux sommets, x et y, représente une dépendance de la classe représentée par x à la classe représentée par y. Un arc est marqué par le type de dépendance qui existe entre les deux classes, à savoir dépendances d'utilisation (association ou dépendance UML) ou bien héritage.
Un GDC peut être facilement construit à partir d'un diagramme de classes UML, les règles de transformation sont résumées REF _Ref20113132 \h Figure 60. Ces transformations sont rendues explicites dans les définitions suivantes. Notez que, puisqu'il y a un sommet pour chaque classe et chaque sommet représente une et une seule classe, dans les définitions suivantes, le sommet correspondant à une classe c s'appelle simplement c.
Étiquettes darc. Chaque arc dans un GDC représente une dépendance entre deux classes d'un système orienté objet. Larc entre les sommets c ( C et d ( C est marqué par le type de dépendance existant entre c et d. Les dépendances peuvent être de deux types : utilisation (étiquette U) si c utilise d ou héritage (étiquette I) si c ( d et c hérite de d.
Étiquette U. Nous associons un ensemble de méthodes à l'étiquette U qui correspond à l'ensemble de méthodes dans M(d) employé par la classe c. La valeur par défaut de cet ensemble de méthodes est M(d) (tant que nous ne connaissons pas le sous-ensemble M(d) utilisé par c). Cette transformation est illustrée dans la REF _Ref20113132 \h \* MERGEFORMAT Figure 60(a). Dans le cas dune dépendance d'utilisation stéréotypé par «instantiate» ou «create» entre les classes c et d, l'ensemble de méthodes associées à l'étiquette U est (created ()) et indique que c appelle seulement la méthode de création de classe d à travers cette relation d'utilisation.
Étiquette I. L'étiquette d'héritage est dérivée en deux étiquettes : I-child et I-parent ( REF _Ref20113132 \h \* MERGEFORMAT Figure 60(b)). Si c ( C, d ( C -{c}, et c hérite directement de d, alors il y a un arc (d,c) marqué I-child et un arc (c,d) marqué I-parent. Si d est une interface pure, larc(c,d) n'existe pas (dans ce cas, c ne peut pas employer des méthodes de d, REF _Ref20113132 \h \* MERGEFORMAT Figure 60(c)), et si la classe d dépend d'autres classes, les arcs marqués U de d n'existent plus, mais sont déplacés aux enfants concrets de d (la REF _Ref20113132 \h \* MERGEFORMAT Figure 60(d)).
Figure SEQ Figure \* ARABIC 60 Les transformations de base d'un diagramme de classes UML en un GDC
Du point de vue du test, nous avons besoin d'une dépendance du parent à l'enfant, parce que toute référence à un objet de type parent peut désigner un objet fils. Ainsi, toute relation à une classe parent se propage vers chaque classe fille. La dépendance de la classe fille à la classe parent est évidente: c emploie d quand elle appelle une méthode m héritée de d.
La REF _Ref20113186 \h Figure 61 donne lexemple dun graphe de dépendance de classes obtenu à partir d'un petit diagramme de classes, en appliquant les règles de transformation données dans les définitions ci-dessus.
Figure SEQ Figure \* ARABIC 61 Exemple GDC
Détecter des anti-patterns à partir du GDC
Nous revenons maintenant à la notion danti-pattern, et les définissons en termes de topologie remarquable dans un graphe. Pour cela nous rappelons les définitions de chemin et de cycle dans un graphe qui seront nécessaires pour définir les anti-patterns.
Chemin. Un chemin C dans un GDC est une séquence de sommets C = [xi1, xi2, xi3,
, xik] tels que :
(xi1, xi2) ( ( , (xi2, xi3) ( ( ,
, (xik-1, xik) ( (
xi1 est lorigine du chemin et est appelé origine(C)
xik la fin, fin(C)
les xij (2 ( j ( k-1), sont les sommets intermédiaires (cet ensemble de sommets est appelé sommetsIt(P)).
Cycle. Soit C un chemin, C est un cycle ssi fin(P) = origine(P).
Chemin et cycle élémentaire. Un chemin élémentaire est une séquence de sommets tels quil ny ait jamais deux fois le même sommet. Un cycle élémentaire est un chemin élémentaire dans lequel seul le sommet dorigine est répété une fois.
Sur la REF _Ref20113186 \h Figure 61, [c, b, b2, b22] ou [c, b, b2, b21, e] sont des chemins élémentaires, mais [c, b, b2, b21, b2] nen est pas un. De même, [c, b, b2, c] est un cycle élémentaire, mais pas [c, b, b2, b21, b2, c].
Interaction De Classe (IC). Il existe une interaction de classe IC(c,d) de la classe c ( C à la classe d ( C -{c}, s'il existe au moins deux chemins élémentaires P1 et P2 tels que P1 ( P2 et (origine(P1) = origine(P2)=c) ( (end(P1) = end(P2) = d) ( (sommetsIt(P1) ( sommetsIt(P2)).
Un chemin traversant une hiérarchie d'héritage ne doit la traverser que dans une seule direction, c.-à-d. quon ne doit avoir que des arcs allant des sommets enfants aux sommets parents ou des arcs allant des sommets parents aux sommets enfants.
Figure SEQ Figure \* ARABIC 62 IC sur un GDC
Cette définition de l'interaction ne tient compte que des interactions unitaires. Par exemple, sur le GDC de la REF _Ref20113202 \h Figure 62, deux interactions sont détectées : IC(a, c) et IC(d, f), tandis que la plus grande interaction IC(a, f) n'est pas détectée. Nous supposons quil est suffisant de ne détecter que les interactions, puisque la résolution de IC(a, c) et IC(d, f) résout également IC(a, f).
Auto Utilisation (AU). Il existe une interaction dauto-utilisation AU(c) sur la classe c ( C, s'il existe un cycle élémentaire d'origine c. Lanti-pattern concerne tous les nuds du cycle.
Un cycle traversant une hiérarchie d'héritage doit croiser la hiérarchie seulement dans une direction, c.-à-d. quil ne doit y avoir que des arcs allant des sommets enfants aux sommets parents ou des arcs allant des sommets parents aux sommets enfants.
Figure SEQ Figure \* ARABIC 63 AU Sur un GDC
La REF _Ref20113217 \h Figure 63 montre un graphe sur lequel une interaction AU(c) peut être détectée: il y a un cycle élémentaire du sommet c au sommet c. Tout comme l'interaction IC, la définition de l'interaction AU donnée ci-dessus considère seulement les interactions unitaires.
Complexité des anti-patterns
La complexité d'un anti-pattern peut maintenant être formalisée en tenant compte du polymorphisme dans le système. La complexité d'une interaction augmente quand un ou plusieurs chemins impliqués traverse un graphe correspondant à une hiérarchie d'héritage.
Complexité dune interaction. Soit {P1
PnbPaths} un ensemble de chemins correspondant à une interaction de classe IC, la complexité de l'interaction est liée à la complexité des différents chemins de la façon suivante:
EMBED Equation.3
Chemin de descendance. Dans une hiérarchie d'héritage, un chemin de descendance est l'ensemble des classes croisées par un chemin allant de la classe racine à une classe feuille.
Comme défini plus tôt, les chemins impliqués dans une interaction peuvent passer par une hiérarchie d'héritage seulement dans une direction. Nous identifions alors un sous-chemin correspondant à une tranche de la hiérarchie d'héritage allant d'une classe racine à une classe feuille comme le montre la REF _Ref20113236 \h Figure 64. Ce sous-chemin s'appelle un chemin de descendants dans une hiérarchie d'héritage. Si un chemin impliqué dans une interaction passe par une ou plusieurs classes d'un chemin de descendants dans le graphe, la complexité de linteraction augmente de la façon suivante: s'il y a n classes dans un chemin de descendants, dont aucune nest une interface pure, la complexité du sous composant est n*(n-1). Chaque classe a une relation avec chacune des (n-1) autres classes et n*(n-1) interactions peuvent se produire et doivent être examinées.
Figure SEQ Figure \* ARABIC 64 Tranche dans un SCC correspondant à une hiérarchie d'héritage
Si deux hiérarchies d'héritage sont présentes dans linteraction, chaque classe d'une hiérarchie peut avoir une relation avec chaque classe de l'autre hiérarchie. Aussi, la complexité d'un chemin est-elle le produit de la complexité associée à chaque hiérarchie croisée par l'interaction.
Complexité d'un chemin dans une interaction de classe. Soit P un chemin impliqué dans une interaction de classe, et IHi,
, IHnbCrossed les hiérarchies d'héritage croisées par P, la complexité de P est calculée de la manière suivante :
complexity(P) = EMBED Equation.3
Les arcs des hiérarchies d'héritage ne sont pas pris en compte dans le calcul de la complexité.
Par la suite, nous définissons la complexité d'un chemin traversant une hiérarchie dhéritage. Plusieurs chemins de descendants dans une hiérarchie d'héritage peuvent augmenter la complexité d'un chemin. Si un chemin passe par une classe qui n'est pas une feuille dans la hiérarchie d'héritage, il peut y avoir plusieurs chemins de descendants incluant cette classe. Par exemple, sur la REF _Ref508613502 \h Figure 65, le chemin [bEvt, bSt, book] traverse la classe racine de la hiérarchie dhéritage de BookState. Puisque BookState nest pas une classe feuille dans larbre dhéritage, tous les chemins de descendants commençant par le nud bSt doivent être pris en compte dans le calcul de la complexité du chemin. Donc, les 5 chemins de descendants [bSt, Or], [bSt, Bd], [bSt, IL, BFx], [bSt, IL, Av], [bSt, IL, Rd] sont impliqués dans le calcul de la complexité du chemin [bEvt, bSt, book].
Complexité d'un chemin passant par une hiérarchie d'héritage. Soit IH une hiérarchie d'héritage et P un chemin croisant IH, la complexité d'IH pour P est l'addition de la complexité de dp1,
, dpnbDP , des chemins de descendance dans IH influençant la complexité de P.
complexiéy(IH,P) = EMBED Equation.3
La complexité d'un chemin de descendants correspond au nombre maximum de dépendances entre les classes dans ce chemin. Dans le pire cas, chaque classe dépend de toutes les autres, ainsi, s'il y a n classes dans le chemin, il y aura donc tout au plus, n*(n-1) interactions.
Complexité d'un chemin de descendants. Soit DP un chemin de descendance de hauteur h, la complexité pour DP est alors :
complexité(dp) = EMBED Equation.3
Dans la suite, ces définitions sont illustrées par le calcul de la complexité sur lexemple du système de gestion de livres.
Mesure de la complexité des anti-patterns : système de gestion de livres
Figure SEQ Figure \* ARABIC 65 GDC pour le gestionnaire de livres
Le GDC pour le gestionnaire de livres de la REF _Ref27540156 \h Figure 53 est donné REF _Ref508613502 \h Figure 65. A partir de ce graphe, nous détaillons maintenant le calcul de la complexité de linteraction de classes entre BookEvent et Book. La complexité de cette interaction est le produit de la complexité des deux chemins impliqués : C1 = [bEvt, book] et C2 = [bEvt, bSt, book].
Même si C1 se résume à un arc simple entre deux nuds, il a une complexité non nulle puisque la classe BookEvent fait partie dun arbre dhéritage. La complexité de C1 est donc la somme des complexités de chaque chemin de descendants impliqué. Comme la classe BookEvent est la racine de larbre dhéritage, tous les chemins de descendants sont impliqués dans le calcul de la complexité. Tous ces chemins sont de taille 2, et ont donc tous la même complexité. Il y a 7 chemins de descendants, et la complexité pour cet arbre dhéritage est : 7*2*(2-1)=14. Cest aussi la complexité de C1.
Le chemin C2 traverse les nuds bEvt et bSt qui correspondent à deux classes racines darbres dhéritage. La complexité de larbre sous le nud bEvt est 14. La complexité pour le second arbre dhéritage est laddition de tous les chemins de descendance dans cet arbre. Deux chemins sont de longueur 2 et trois de longueur 3, la complexité est : 2*(2-1)+2*(2-1)+3*(3-1)+3*(3-1)+3*(3-1) = 22. La complexité de C2 est le produit des deux complexités, ce qui correspond au fait que les classes dun arbre dhéritage peuvent potentiellement interagir avec toutes les classes de lautre arbre. Le chemin C2 a une complexité de 22*14=308.
La complexité totale pour linteraction de classe est égale au produit des complexités des chemins C1 et C2. Cette complexité correspond au nombre maximum dinteractions classe à classe, et ne tient pas compte du fait quun grand nombre de classes ne dépendent jamais lune de lautre. Par exemple la classe SetDamaged ninteragit avec aucune classe détat. La complexité dun anti-pattern est une borne maximum pour le nombre de relations qui devraient être couvertes au moment du test. Dans la section suivante, nous montrons que lajout de stéréotypes sur les associations et dépendances du diagramme de classes permettrait dignorer certains arcs au moment du calcul de la complexité dun anti-pattern, et donc dobtenir une valeur plus proche de la complexité réelle des interactions dobjets.
Amélioration de la testabilité du modèle
Améliorer la testabilité du logiciel, en ce qui concerne notre critère de test, signifie éviter les interactions d'objets. Comme nous lavons suggéré dans la section REF _Ref38688599 \r \h 5.3.3 une solution consiste à clarifier le modèle, de sorte que le code soit aussi proche que possible de ce que le concepteur veut.
Une première solution consiste à utiliser des refactorings ADDIN EN.CITE Opdyke19921972Opdyke, William F.1992Refactoring object-oriented frameworksUrbana-Champaign, IL, USAUniversity of Illinois151PhD[Opdyke''92] sur le modèle pour remplacer, quand cest possible, une classe abstraite par une classe dinterface. Cette action permet de diminuer la complexité des anti-patterns, puisque dans ce cas les arcs qui entraient dans cette classe disparaissent.
Une seconde solution est fondée sur lutilisation de stéréotypes UML. En effet, UML permet à un utilisateur de définir des stéréotypes pour associer une sémantique aux éléments dun modèle. Nous définissons ainsi plusieurs stéréotypes qui indiquent la sémantique des liens impliqués dans des anti-patterns (association, dépendance, agrégation, composition). Grâce à ces éléments de spécification supplémentaires, le programmeur devrait éviter dimplanter une interaction d'objets. Les stéréotypes présentés ici sont analogues d'une certaine manière aux critères de test flots de données ADDIN EN.CITE Rapps1985520Rapps, S. Weyuker, E. J.1985Selecting Software Test Data Using Data Flow InformationIEEE Transactions on Software Enginnering114367 - 375April[Rapps''85], qui identifient la "définition" et l"utilisation" des variables dans un programme. Ce modèle de test classique vise à déterminer le flot de données, la "ligne de vie" des variables au niveau unitaire.
Nous proposons quatre stéréotypes:
« create » : cest un stéréotype standard dUML. Ce dMLdun stéréotype sur un lien dune classe a à une classe b signifie que les objets du type a appellent la méthode de création (ou constructeur) de la classe b. Si aucun stéréotype « use » n'est attaché au même lien, seule la méthode de création peut être appelée.
« use » : un stéréotype « use » sur un lien de la classe a à la classe b signifie que les objets du type a peuvent appeler n'importe quelle méthode à l'exclusion des méthodes de création des objets du type b. Les stéréotypes suivants permettent daffiner cette définition:
« use_consult »: une spécialisation du stéréotype « use » où les méthodes appelées ne modifient jamais les attributs des objets de type b.
« use_def »: une spécialisation du stéréotype « use » où au moins une des méthodes appelées modifie des attributs des objets de type b.
Par défaut, labsence de stéréotype sur un lien équivaut à une combinaison de « use » et « create ».
Les stéréotypes sont pris en compte au moment de la génération du graphe en associant une autre valeur aux étiquettes de U. Ceci permet également au concepteur d'estimer l'amélioration du modèle après avoir ajouté des stéréotypes. Cette analyse correspond à l'étape 2 de la méthodologie proposée dans la section REF _Ref29203471 \r \h 5.2.2. L'utilisation des stéréotypes modifie l'identification des interactions d'objets comme suit.
Assertion 1. Interactions d'objets. Soit P1 et P2 deux chemins de la classe c vers la classe d, définissant une interaction de classe entre c et d. Soit e1 larc entrant du nud fin(P1), et e2 larc entrant le nud fin(P2), une interaction d'objet existe ssi :
e1 et e2 sont stéréotypés « use » ou « use_def ».
Assertion 2. Interaction d'objets d'auto utilisation. Soit P un chemin de la classe c à elle-même, définissant ainsi une interaction d'auto utilisation pour c. Soit e larc entrant du nud fin(P), une interaction d'objet d'auto utilisation existe ssi :
e est stéréotypé « use » ou « use_def ».
Figure SEQ Figure \* ARABIC 66 Une interaction de classe entre C et D
La REF _Ref20113264 \h Figure 66 illustre une interaction de classes. Les chemins allant de c à d et qui ne se terminent pas avec un arc stéréotypé « use » ou « use_def », ne causent pas une utilisation contradictoire de la classe d par la classe c. Chaque chemin peut être examiné indépendamment en utilisant les critères de test quAlexander propose dans ADDIN EN.CITE Alexander200023Alexander, R. T.Offutt, Jeff2000Criteria for Testing Polymorphic RelationshipsISSRE'00 (Int. Symposium on Software Reliability Engineering)San Jose, CA, USA15 - 23OctoberD:\Travail\biblio-bb\TestOO\CriteriaForPolymorph.pdf[Alexander''00].
La conformité du code avec les contraintes des stéréotypes peut être vérifiée automatiquement. Par exemple, la vérification d'un stéréotype «use-consult» de c à d consiste à vérifier que c nappelle que les méthodes de consultation de d et que les méthodes de consultation de d ne modifient jamais l'état de d. Dans le cas dune rétro-ingénierie, ces stéréotypes peuvent être déduits dune analyse statique en utilisant, par exemple, la méthode décrite par Richner et Ducasse dans ADDIN EN.CITE Richner19991983Richner, TamarDucasse, Stéphane1999Recovering high-level views of object-oriented applications from static and dynamic informationICSM'99 (International Conference on Software Maintenance )Oxford, UK13 - 22September[Richner''99].
La section suivante illustre des problèmes potentiels de testabilité sur trois exemples, et illustre différentes solutions pour éviter des problèmes réels au niveau de limplantation. Des stéréotypes sont présentés directement par le concepteur qui veut spécifier le logiciel avec plus de précision. Par ailleurs, nous voyons dans la suite que des stéréotypes peuvent également être présentés automatiquement, au méta-niveau dUML pour des design patterns. Cette méta programmation d'un modèle employant des collaborations paramétrées est détaillée dans le section REF _Ref42573195 \r \h 5.8.3, après la discussion de la testabilité des design patterns.
Exemples d'application
Cette section illustre les différentes techniques pour améliorer la testabilité dun diagramme de classes sur trois exemples : le système de gestion de livres, un serveur de réunion virtuelle et un compilateur. Létude de ces résultats permet de repérer les points dans une conception qui peuvent entraîner une implantation difficile à tester sils sont mal interprétés.
Le gestionnaire de livres
Le GDC pour le système de gestion de livres est présenté REF _Ref508613502 \h Figure 65. Dans la section REF _Ref29374575 \r \h 5.4.4 nous avons calculé la complexité pour linteraction de classe IC(BookEvent, Book) et nous avons mentionné que de nombreuses interactions sont infaisables, et ne devraient donc pas être prises en compte pour le calcul de la complexité. Par la suite, plusieurs stéréotypes ont été définis dans le but de préciser le rôle des liens dans le diagramme de classes. Ceci permet de prendre en compte de manière différente les différents types de relations au moment du calcul de la complexité.
Linteraction de classe IC(bEvt, book) peut être supprimée en spécifiant que la dépendance entre les classes BookEvent et Book nest utilisée quen lecture. La dépendance peut donc être stéréotypée «uses_consult», et il ny a plus dinteraction de classes. Cependant, le chemin reliant BookEvent à Book en traversant BookState reste toujours complexe pour le test. Cette partie du diagramme peut être améliorée pour la testabilité en transformant les classes BookEvent et BookState en classes dinterface. Ceci éviterait les interactions entre ces classes et leurs classes filles et ferait passer la complexité de ce chemin de 308 à 56. De la même manière, la complexité des deux interactions dauto-utilisation SU1(book) et SU2(book) peut être réduite en transformant les classes BookEvent et BookState en classes dinterface.
Serveur De Réunion Virtuelle
Figure SEQ Figure \* ARABIC 67 Le serveur de la réunion virtuelle
Le serveur de réunion virtuelle vise à simuler des réunions de travail. Une fois relié au serveur, un participant peut entrer ou sortir d'une réunion, parler, ou planifier de nouvelles réunions. Il existe trois types de réunions :
Réunions standards où le participant qui a la parole est désigné par un modérateur (nommé par l'organisateur de la réunion).
Réunions démocratiques : des réunions standards où le modérateur est un robot FIFO (le premier participant à demander la permission de parler est le premier à parler).
Réunions privées : des réunions standards avec l'accès limité à un ensemble défini de participants.
Toutes les commandes possibles sont réifiées et héritent de l'interface command. Les états internes possibles d'un client et d'une réunion sont contrôlés par le pattern state. La REF _Ref20113274 \h Figure 67 donne le diagramme de classe du serveur de la réunion virtuelle.
Le graphe de dépendance de classe du serveur de la réunion virtuelle est donné REF _Ref20113683 \h Figure 68. Un grand nombre d'interactions de classe sont détectées sur ce modèle, et nous ne les détaillons pas toutes ici. Nous insistons juste sur les configurations intéressantes et montrons que même sur un modèle simple (29 classes), beaucoup de problèmes de test émergent.
Figure SEQ Figure \* ARABIC 68 GDC du serveur de la réunion virtuelle
Il y a deux interactions d'auto-utilisation autour des noeuds User et Mtg , qui sont dues à l'utilisation du pattern state. Néanmoins, si le pattern state est correctement implanté, cette configuration ne produira en fait aucune interaction dobjets, puisque l'interface state emploie seulement la méthode de son contexte pour modifier l'état courant du contexte, tandis que le contexte délègue tout traitement dépendant de létat à son état courant.
Une interaction de classes est détectée entre Serv et R : le serveur peut accéder à l'interface Ringable par l'intermédiaire de la classe TimerManager ou par la classe DemocraticMtg. Cette interaction de classes pourrait être facilement évitée en transformant la classe abstraite Meeting en interface. Une configuration intéressante d'interactions de classes imbriquées existe entre User, Serv, Mtg et MM. Il y a une interaction de classes IC(Serv, User) entre Serv et User d'un coté, et IC(Mtg, User) entre Mtg et User de lautre. Notez que IC(Serv, User) inclut IC(Mtg, User).
Figure SEQ Figure \* ARABIC 69 Configuration des interactions de classes incluses
Deux remarques peuvent être faites sur cette configuration particulière isolée sur la REF _Ref20113703 \h Figure 69. D'abord, suivant la manière dont l'interaction imbriquée IC(Mtg, User) sera résolue, l'interaction de classes IC(Serv, User) n'est pas nécessairement résolue. Deuxièmement, même s'il n'est pas possible de supprimer CI(Mtg, user), CI(Serv, user) peut être résolue, par exemple en ajoutant au modèle un stéréotype «use-consult» sur l'association de server à meeting. De cette configuration, nous pouvons déduire que les interactions de classes peuvent être combinées de différentes manières; dans certains cas, toutes les interactions de classes ne sont pas nécessairement prises en compte (comme sur la REF _Ref20113202 \h Figure 62), dans d'autres cas, il est nécessaire de traiter toutes les interactions de classes (comme sur la REF _Ref20113703 \h Figure 69).
D'autres interactions de classes peuvent être détectées, par exemple de Mtg à User (où Mtg peut accéder à user directement, par MM ou par le MM et PM) ou de Serv à Mtg.
Une architecture de compilateur
La REF _Ref20113781 \h Figure 70 donne une architecture orientée objet pour un compilateur pris dans ADDIN EN.CITE Jézéquel1999211Jézéquel, Jean-MarcTrain, MichelMingins, Christine1999Design Patterns and ContractsAddison-Wesley3480-201-30959-9[Jézéquel''99]. Cette architecture comprend une classe Scanner qui produit des jetons, une classe parser qui produit un arbre syntaxique abstrait en utilisant Node_builder, et Program_node représentant un nud abstrait dans l'arbre syntaxique.
Le graphe de dépendance de classe dérivé de cette architecture est donné sur la REF _Ref20113738 \h Figure 71. Deux interactions de classe peuvent être détectées sur ce graphe. La première, IC(Fa, PN), est due aux deux chemins [Fa, NB, PN] et [Fa, NV, PN]. La deuxième interaction potentielle, IC(NV, PN), est due aux chemins [NV, Fa, NB, PN] et [NV, PN]. Les deux interactions semblent simples puisquelles ne concernent que quatre classes, liées par des relations simples d'utilisations. Cependant, leur complexité croît énormément en raison des onze classes dans la hiérarchie d'héritage de Program_node : neuf chemins de descendance de taille trois sont impliqués dans IC(Fa,PN) et IC(NV,PN). La complexité globale de cette hiérarchie est
EMBED Equation.3 La hiérarchie d'héritage Node_visitor a un plus petit impact sur la complexité puisqu'elle a seulement deux classes. La complexité pour cette hiérarchie est 4.
Figure SEQ Figure \* ARABIC 70 Architecture Du Compilateur
Comme tous les chemins concernés dans les interactions croisent les mêmes hiérarchies, ils ont tous la même complexité: 54(4 = 216. De la même manière, les deux interactions ont la même complexité qui est le produit de la complexité des deux chemins : 216(54=11664.
Ici, le modèle peut être raffiné avec des stéréotypes sur des associations de façade à node_visitor et de façade à node_builder. En effet, les instances de façade devraient employer des instances de node_visitor seulement pour des consultations, l'association est ainsi stéréotypée « use_consult ». L'association de façade à node_builder devrait être stéréotypée « use_def » puisque les instances de façade pourraient changer l'état des instances de node_builder. Si ces stéréotypes sont sur le modèle, le programmeur ne devrait pas implanter d'interactions d'objet.
Jusqu'ici nous nous sommes concentrés sur des anti-patterns de testabilité sur le modèle globale dun système. Nous avons montré comment détecter ces configurations sur un diagramme de classes, puis nous avons proposé des règles pour préciser le modèle afin de réduire la complexité de tels anti-patterns. De toutes nos études, il s'est avéré qu'il peut être très difficile de résoudre ces problèmes de testabilité de manière globale pour un système. Une solution serait de résoudre les problèmes dans des sous-systèmes. Les sections REF _Ref43735513 \r \h 5.7 et REF _Ref43735553 \r \h 5.8 se concentrent sur les design patterns, qui apparaissent en tant que sous-systèmes logiques et bien définis dans un diagramme de classes global. La section REF _Ref43735513 \r \h 5.7 détaille les avantages en termes de testabilité de concevoir en utilisant des patterns et la section REF _Ref43735553 \r \h 5.8 se concentre sur la résolution des problèmes de testabilité pour des applications de design pattern.
Figure SEQ Figure \* ARABIC 71 GDC pour l'architecture du compilateur
Design Patterns pour la testabilité du modèle
Jusqu'a récemment, une architecture OO finale apparaissait souvent comme un ensemble complexe de classes agissant l'une sur l'autre en labsence de sous-ensembles logiques émergeant du modèle global. Cependant, grâce à UML, des méthodologies systématiques telles que CatalysisSM ADDIN EN.CITE D'Souza199851D'Souza, D. F.Wills, A.C1998Object, Components and Frameworks with UML, The Catalysis ApproachObject TechnologyAddison-Wesley0-201-31012-0[D'Souza''98] offrent maintenant une approche de décomposition de l'architecture. Ces méthodologies aident à concevoir un logiciel orienté objet comme une succession de raffinements, depuis les premières étapes de lanalyse jusquà l'exécution. En particulier, les design patterns ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95] peuvent servir comme base pour une telle amélioration. À partir d'un diagramme de classes d'analyse, les design patterns aident le concepteur en proposant des solutions de conception pour résoudre des problèmes dans un contexte particulier, et ainsi transformer le diagramme en un autre plus orienté vers l'implantation. Les design patterns correspondent alors a des sous-ensembles dans le diagramme de classes, et peuvent être considérés comme une structure intermédiaire entre l'architecture globale et la classe simple. Cette décomposition du système fournit une solution intéressante pour résoudre certains problèmes à un niveau local quand la solution au niveau global est trop complexe.
Concevoir par cristallisation de patterns
Design patterns. Les design patterns représentent des solutions aux problèmes qui surgissent quand un logiciel est développé dans un contexte particulier. Les design patterns peuvent être considérés en tant que microarchitectures réutilisables qui contribuent à une architecture du système globale; ils saisissent les structures dynamiques et statiques et les collaborations entre les principaux participants du modèle de conception.
La REF _Ref20113799 \h Figure 72 montre un premier modèle orienté objet (étape d'analyse) pour un client de messagerie instantanée. La REF _Ref20113814 \h Figure 73 illustre une conception détaillée finale possible après plusieurs étapes d'amélioration, et montre des instances de design patterns dans des ellipses selon la norme UML.
Figure SEQ Figure \* ARABIC 72 Un client de messagerie instantanée (diagramme d'analyse)
Figure SEQ Figure \* ARABIC 73 Un modèle « final » possible pour le client de messagerie instantanée
L'architecture de la REF _Ref20113814 \h Figure 73 est une conception orientée objet typique obtenue après des étapes de cristallisation. Une étape de cristallisation comporte l'adaptation d'un design pattern à un diagramme de classes.
Cette approche est une méthode largement répandue pour passer d'un premier diagramme d'analyse vers un autre plus orienté vers lexécution. Après l'application d'un design pattern, les classes principales d'analyse demeurent sur le diagramme. De nouvelles classes et liens apparaissent et quelques relations d'associations sont supprimées. Dans lexemple de la REF _Ref20113814 \h Figure 73, le diagramme de classes d'analyse a été modifié à travers trois étapes indépendantes qui correspondent à l'adaptation et la combinaison du pattern Abstract Factory et de deux patterns State. Les nouvelles relations et classes introduites pendant le raffinage du modèle en employant des design patterns semblent rendre la conception très dure a tester ; en particulier chaque classe peut potentiellement interagir avec toute autre. Cependant, la méthodologie de développement (cristallisation des design patterns à partir d'une première analyse) nous permet réellement de considérer la totalité du modèle plus comme une composition de microarchitectures que comme un ensemble monolithique de classes inter-connectées. Par conséquent, la complexité globale est décomposée en une combinaison de complexités de microarchitectures, et la tâche de test est ainsi simplifiée. En effet, une fois que les sous-ensembles sont identifiés dans le modèle, plusieurs problèmes de test peuvent être résolus à un niveau local (microarchitecture). Les questions sont:
Quelles sont les améliorations pour la testabilité obtenues en utilisant les design patterns?
Les problèmes de testabilité peuvent-ils être localisés (confinés) au sous-ensemble du diagramme qui correspond à l'application d'un design pattern?
Nous répondons à la première question dans le contexte d'une comparaison de testabilité entre l'utilisation isolée du design pattern State et l'implantation procédurale classique d'une machine d'états en utilisant une seule classe.
Nous discutons de la deuxième question - définition de règles/contraintes sur le modèle qui évitent des anti-patterns de testabilité - à travers une solution qui attache des contraintes à un modèle telles que l'implantation soit testable.
Application du design pattern State pour améliorer la testabilité
Nous distinguons la programmation orientée objet de la programmation procédurale par l'effort consacré à la modélisation. Nous considérons le logiciel orienté objet comme un logiciel pour lequel la majeure partie de l'effort est consacrée à la conception de l'architecture, la décomposition modulaire (classe), et le couplage entre les classes (en utilisant autant que possible des constructions orientées objet particulières telles que l'héritage et le polymorphisme). Nous illustrons les différences entre programmation fonctionnelle et OO grâce à deux implantations d'une machine d'états.
L'exemple, pris dans ADDIN EN.CITE Jézéquel1999211Jézéquel, Jean-MarcTrain, MichelMingins, Christine1999Design Patterns and ContractsAddison-Wesley3480-201-30959-9[Jézéquel''99], implante la machine d'états liée à la classe mailer ( REF _Ref20113848 \h Figure 74). Cette classe définit une interface publique pour envoyer et recevoir des messages indépendamment du statut de la connexion réseau. Quand la connexion réseau est disponible, les messages sont simplement expédiés. Si elle est indisponible, les messages à diffuser sont stockés dans le serveur de messagerie en attendant la transmission quand la connexion réseau sera restaurée.
Figure SEQ Figure \* ARABIC 74 Machine d'états pour la classe Mailer
Une implantation procédurale de cette machine d'états consiste à coder la machine comme une seule classe. Par exemple, le corps de chacune des méthodes dépendantes de létat courant est une grande conditionnelle. De telles méthodes (par exemple send()) vérifient la valeur de l'état courant, et exécutent un traitement différent en fonction de cette valeur. Ceci est illustré sur la REF _Ref20113858 \h Figure 75. Il y a au moins deux arguments contre l'emploi de ce type de mise en uvre. D'abord, il tend à rendre le code peu clair, difficile à lire, à maintenir ou réutiliser. En second lieu, il rend le logiciel difficile à étendre: dans ce cas particulier, lajout dun nouvel état impose l'ajout d'un nouveau cas dans chacune des méthodes dépendantes de létat, cest-à-dire que des changements sont exigés au niveau du code de plusieurs méthodes. Par conséquent, le risque de commettre une erreur augmente.
Figure SEQ Figure \* ARABIC 75 Machine d'états implantée dans une seule classe
Par ailleurs, cette implantation doit être testée pour valider chaque méthode. Une fois que l'instance de la classe est dans un état particulier, chaque méthode est examinée pour cet état et pour chaque transition existant depuis cet état. Ainsi, le nombre d'instructions pour examiner cette classe est de l'ordre du nombre moyen d'instructions requis pour mettre la classe dans un état donné, multiplié par le nombre moyen de transitions sortantes, plus le nombre d'instructions dont on a besoin pour tester son comportement.
Figure SEQ Figure \* ARABIC 76 Machine d'états implantée avec le design pattern state
Maintenant, implantons la machine d'états avec le pattern state ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95], l'architecture est donnée REF _Ref20113867 \h Figure 76. Elle contient la classe mailer et l'interface mailerstate. Le mailerstate rassemble chaque méthode dont le traitement dépend de l'état courant. Il y a une classe concrète d'état pour chaque état possible. Dans la classe mailer, le traitement des méthodes dépendantes de l'état courant est délégué à l'objet currentState, qui peut être lié dynamiquement à n'importe quel classe héritant de la classe mailerstate. Par conséquent, le contrôle de la valeur de l'état ne se fait plus explicitement dans la classe mailer. Le bon traitement d'une méthode est fait grâce à la liaison dynamique. Ceci rend la classe mailer beaucoup plus claire, et complètement indépendante de sa machine d'états. Les deux problèmes détectés dans la solution précédente sont résolus. Le code de chaque méthode est maintenant beaucoup plus petit (chaque méthode exécute seulement une opération), et ainsi plus lisible. De plus, l'architecture facilite le changement de la machine d'états. Par exemple, ajouter un nouvel état consiste à ajouter une nouvelle classe concrète pour cet état.
Plusieurs éléments rendent la conception "pattern" plus testable que la conception "classique", même si elle semble plus compliquée en termes de nombre de classes ou en termes de couplage entre ces classes. D'abord, la structure de contrôle complexe de l'implémentation précédente a disparu. Le contrôle n'a pas besoin d'être testé en tant que tel, puisqu'il est implanté grâce à la liaison dynamique. Ensuite, le test est simplifié puisque les comportements spécifiques pour chaque état sont isolés dans différentes classes. Ainsi, il ny a plus besoin du préambule qui met l'objet dans un état particulier avant de le tester. En effet, lexécution d'une méthode pour un état donné se fait directement en exécutant cette méthode sur une instance de la classe correspondant à cet état.
Cependant, de nouveaux problèmes de test surgissent, qui semblent spécifiquement liés à la conception orientée objet, et plus particulièrement, à la microarchitecture correspondant à lapplication du design pattern.
Analyse de testabilité des design patterns
Nous nous intéressons maintenant à la résolution des anti-patterns de testabilité à un niveau local, pour diminuer la complexité de la testabilité du système global (cf. section REF _Ref29203350 \r \h 5.4.3). Nous illustrons l'analyse de testabilité proposée (voir section REF _Ref43735349 \r \h 5.4) sur deux microarchitectures, correspondant aux applications des design patterns Abstract Factory et State. Le choix d'utilisation des diagrammes UML/OMT pour décrire la structure du design pattern dans ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95] mène à plusieurs erreurs d'interprétation. Par ailleurs, l'introduction de stéréotypes appropriés pour clarifier la conception doit être faite à la main. Nous proposons ainsi d'employer des diagrammes de collaboration comme représentation pour décrire les design patterns ADDIN EN.CITE Le Guennec2000903Le Guennec, AlainSunyé, GersonJézéquel, Jean-Marc2000Precise Modeling of Design PatternsUML'00Springer-Verlag1939482 - 496Lecture Notes in Computer ScienceOctoberSunyé2000913Sunyé, GersonLe Guennec, AlainJézéquel, Jean-Marc2000Design Pattern Application in UMLECOOP'00185044 - 62Lecture Notes in Computer ScienceJune[Le Guennec''00; Sunyé''00]. Dune part, cette représentation est plus adaptée à la description des aspects statiques et dynamiques des patterns. Dautre part, elle peut servir à une application automatique des design patterns sur un diagramme de classes. La définition des contraintes pour la testabilité au niveau méta permet alors une application automatique des stéréotypes sur le modèle.
Analyse de testabilité de State
Figure SEQ Figure \* ARABIC 77 Une application du Design Pattern State
Nous appliquons lanalyse de testabilité (section REF _Ref43735349 \r \h 5.4) sur une instance du design pattern State dont le diagramme de classes est donné sur la REF _Ref20113876 \h Figure 77. Ceci nous permet de détecter avec plus de précision les problèmes de testabilité spécifiques à l'application du pattern State évoqués à la fin de la section REF _Ref43735513 \r \h 5.7. Dans cette application particulière l'association entre TCPConnection et TCPState est bidirectionnelle : TCPConnection garde une référence sur son état courant, et chacun des états concrets doit connaître son contexte pour changer la valeur de létat courant. Cette association bidirectionnelle cause des interactions multiples d'auto utilisation autour de TCPConnection.
Une solution pour améliorer la testabilité du diagramme de classes de la REF _Ref20113876 \h Figure 77 est demployer autant que possible des interfaces à la place des classes abstraites ; ceci évite les liens partant des classes filles vers les classes mères. Si seules les classes feuilles dans la hiérarchie dhéritage (classes qui n'ont aucun descendant) sont des classes concrètes, la complexité de l'interaction traversant cette hiérarchie est réduite (il n'y a plus d'interactions entre les classes dans la hiérarchie).
Le test de l'application du pattern State consiste à vérifier que les méthodes de la classe TCPConnection dépendant de létat courant ne font pas autre chose qu'une délégation vers lattribut currentState. Par ailleurs, il faut vérifier si les états concrets n'appellent pas les méthodes du contexte qui sont déléguées à l'attribut currentState. Si ces contraintes sont vérifiées, il n'y a aucune interaction puisque nous pouvons assurer qu'il ny aura pas d'interactions dauto utilisation.
En appliquant le pattern State comme dans ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95], trois anti-patterns de testabilité sont détectés (des interactions potentielles d'AU). Si toutes les classes abstraites sont converties en interfaces, les trois interactions demeurent mais sont moins complexes. Enfin, si la contrainte de délégation du modèle est vérifiée, il n'y aura plus d'interaction dangereuse.
Analyse de testabilité d'Abstract Factory
La REF _Ref20113899 \h Figure 78 montre une application du design pattern Abstract Factory prise de ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95]. Une interaction de classes apparaît entre la classe Client et chaque classe produit concret (Window ou ScrollBar concrètes) : la classe Client peut utiliser un produit concret directement ou à travers la hiérarchie d'héritage de WidgetFactory.
Comme pour le pattern State, une règle informelle pour améliorer la testabilité du diagramme de classes de la REF _Ref20113899 \h Figure 78 consiste à employer autant d'interfaces que possible. Pour tester l'application du pattern Abstract Factory, nous devons vérifier si la délégation de Client à WidgetFactory crée tous les objets et ne fait pas autre chose. Si l'application du design pattern est correcte, la classe Client doit employer les méthodes de création des produits concrets a travers la hiérarchie d'héritage de WidgetFactory et les autres méthodes par appel direct aux instances des produits concrets. Dans ce cas il n'y a aucun anti-pattern puisque les chemins concurrents entre le client et les produits sont utilisés pour différents buts. En appliquant l'Abstract Factory telle que décrite dans ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95] ( REF _Ref20113899 \h Figure 78), quatre interactions de classe peuvent être détectées. Si toutes les classes abstraites sont converties en interfaces, les quatre interactions demeurent mais sont moins complexes. Enfin, si la délégation est bien appliquée, toutes les interactions de classe sont résolues.
Figure SEQ Figure \* ARABIC 78 Une application du Design Pattern Abstract Factory
Pour ce qui concerne la bonne implémentation de la délégation, la solution serait d'exprimer des contraintes d'implantation sur le modèle pour spécifier clairement la délégation. Cependant, de telles contraintes devraient être exprimées au niveau du méta modèle, puisqu'elles concernent des éléments UML et des concepts non modélisés. Plus précisément, il est impossible d'exprimer avec lOCL qu'une méthode a tout au plus une action à exécuter et que cette action devrait appeler une autre méthode. Un stéréotype «delegation» pourrait être un contournement possible pour ce problème, puisqu'un stéréotype peut avoir des contraintes de méta niveau. Cependant, cette solution n'est pas satisfaisante, puisqu'un stéréotype n'a pas de paramètres et ainsi ne pourra identifier la méthode déléguée. Pour résoudre ce problème, nous employons une autre représentation de design patterns, qui permet dexprimer des contraintes sur leur implantation, et ainsi de supprimer les anti-patterns.
Définir des contraintes de testabilité au niveau méta
Le diagramme de structure UML/OMT du design pattern dans ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95] représente des modèles et peut ainsi décrire uniquement une occurrence (ou instance) d'un design pattern. Il ne peut saisir leur vraie structure, exprimée en termes de "rôles", joués par les éléments du modèle (classes, attributs, associations et méthodes).
Nous détaillons ici deux solutions pour exprimer des design patterns au méta niveau, la première est fondée sur des collaborations paramétrés UML, et la seconde consiste à étendre le méta modèle UML. L'idée est que des contraintes de testabilité peuvent être attachées à ce méta niveau de sorte que les stéréotypes appropriés soient automatiquement ajoutés chaque fois qu'un concepteur crée une instance dun pattern. Nous illustrons cette approche sur le design pattern Factory
Figure SEQ Figure \* ARABIC 79 Collaboration paramétrée
L'utilisation des collaborations paramétrées dans UML est une approche prometteuse pour décrire la structure d'un pattern. Un exemple de cette notation pour modéliser l'Abstract Factory pattern est présenté sur la REF _Ref20113925 \h Figure 79. Ce pattern est composé de deux rôles classificateurs (Factory et Product) et une contrainte de relation «create». Cependant, daprès ADDIN EN.CITE Sunyé2000913Sunyé, GersonLe Guennec, AlainJézéquel, Jean-Marc2000Design Pattern Application in UMLECOOP'00185044 - 62Lecture Notes in Computer ScienceJune[Sunyé''00], les collaborations présentent toujours un manque et ne peuvent représenter avec précision ce pattern. Plus précisément, les concepteurs ne peuvent pas indiquer que ce ne sont pas des rôles de classes individuelles, mais de hiérarchies de classes. Le concepteur ne peut pas non plus indiquer que Factory devrait avoir une méthode "créatrice".
Figure SEQ Figure \* ARABIC 80 Le design pattern Abstract Factory
La REF _Ref20113936 \h Figure 80 présente le même pattern, en utilisant une notation différente, proposée dans ADDIN EN.CITE Le Guennec2000903Le Guennec, AlainSunyé, GersonJézéquel, Jean-Marc2000Precise Modeling of Design PatternsUML'00Springer-Verlag1939482 - 496Lecture Notes in Computer ScienceOctober[Le Guennec''00], où des patterns sont décrits en tant que collaborations du niveau méta, complétées par des contraintes. Ici, ce modèle se compose de trois rôles : Factory, Product and Creator. Le premier rôle est une " hiérarchie", i.e. un ensemble de classes liées par une relation de généralisation. Le second est un ensemble de « hiérarchies ». Finalement, le troisième rôle décrit un ensemble de "tribus". Une "tribu" ADDIN EN.CITE Eden1999952Eden, A. H.1999Precise Specification of Design Patterns and Tool Support in their ApplicationTel Aviv, IsraelUniversity of Tel AvivPhD[Eden''99] est un ensemble déléments partageant une signature commune. Chaque élément est possédé par un élément de la hiérarchie Factory. Une contrainte est attachée au rôle creator : il doit effectuer une action simple, l'instanciation d'un product (cette contrainte est concrétisée par le lien «create»). Ceci implique que la multiplicité de Creator est équivalente à celle des hiérarchies de product.
Un exemple de ce modèle est présenté sur la REF _Ref20113949 \h Figure 81. Window et ScrollBar jouent le rôle de products, et la hiérarchie de classe WidgetFactory joue le rôle de Factory. Il y a deux clans de méthodes de création dans la hiérarchie WidgetFactory (CreateScrollBar() et CreateWindow()). Cette instance de Factory en combinaison avec une des représentations du méta niveau des REF _Ref20113925 \h Figure 79 ou REF _Ref20113936 \h Figure 80 implique que n'importe quelle dépendance entre WidgetFactory (qui crée une instance du rôle Factory) et Window ou ScrollBar (qui crée une instance du rôle Product) est stéréotypée «create». Ainsi, les stéréotypes pour lamélioration de la testabilité de lapplication du pattern sont automatiquement ajoutés pour éviter l'interaction de classe de la classe client vers les classes product. La tâche suivante est une vérification (qui peut être effectuée statiquement par un CASE tool) de la contrainte «create» sur limplantation.
Figure SEQ Figure \* ARABIC 81 Un exemple du design pattern Abstract Factory
La grille de testabilité des design patterns
Pour guider les concepteurs, nous proposons la grille suivante qui résume les problèmes de testabilité potentiels pour chaque design pattern lorsque le rôle des associations entre les classes nest pas défini explicitement. Nous adoptons les instanciations du modèle proposées par Gamma ADDIN EN.CITE Gamma199561Gamma, E.Helm, R.Johnson, R.Vlissides, J.1995Design Patterns: Elements of Reusable Object-Oriented SoftwareProfessional ComputingAddison-Wesley0-201-63361-2[Gamma''95] puisque nous sommes convaincus que la plupart des concepteurs les prennent comme références. Ceci nous permet de paramétrer chaque pattern comme une fonction des éléments principaux qu'il implique.
Nous nous concentrons sur les design patterns fondamentaux sélectionnés par ADDIN EN.CITE Agerbo1998683Agerbo, EllenCornils, Aino1998How to Preserve the Benefits of Design PatternsOOPSLA'98Vancouver, BC, Canada134 - 143October[Agerbo''98] : Abstract Factory, Bridge, Builder, Composite, Decorator, Flyweight, Proxy, Iterator, Mediator, Memento et State. Il apparaît que les modèles les moins testables sont Mediator et Visitor. Mediator est très semblable au pattern Observer, dont la testabilité a était exhaustivement étudiée par Mc Gregor ADDIN EN.CITE McGregor1999300McGregor, John D.1999Test Patterns: Please Stand ByJournal of Object Oriented Programming12314 - 19June[McGregor''99]. Quant au pattern Visitor, il est particulièrement difficile à tester à cause d'une utilisation étendue du polymorphisme et une exécution fondée sur le « double renvoi ».
Design Patternnombre de participantsnombre dinteractions de classesnombre dinteractions dauto-utilisationAbstract Factory1 client
1 classe factory abstraite
n classes factory concrètes
m produits abstraits
p produits concrets
p( X (n*p
aucunBridgeles parametres nont pas dinfluenceaucunaucunBuilder1 client
1 classe builder abstraite
n builder concrets
p produitsp( X (n*p
(même type que Abstract Factory)
aucunComposite1 client
1 classe composant abstraite
n classes composant concrètes
aucun
n
Decorator1 interface
1 classe composant
1 classe decorator abstraite
n classes decorator concrètes
aucun
aucun
FlyweightComme abs. fact.Comme abs. fact.Comme abs. fact.Iterator1 client
n ConcreteAggregate
n ConcreteIterator
aucun2*n
(n du client au ConcreteIterator, n du client au ConcreteAggregate) Proxy1 classe subject abstraite
1 proxy
1 sujet réel
aucun
aucunChain of responsibility1 client
1 classe handler abstraite
n classes handler concrètes
aucun
nMediator1 classe mediator abstraite
1 classe colleague abstraite
n classes colleague concrètes
m classes mediator concrètes
aucun
m*nMemento1 memento
1 originator
1 caretaker
aucun
aucunState1 classe context
1 classe abstraite state
n classes state concrètes
(délégation implicite des classes state concrètes vers context)
no
aucunVisitorn éléments visités
(ConcreteElement)
p visiteurs
(ConcreteVisitor)
aucun
n*pConclusion
Au cours de ce chapitre, nous avons étudié la question de la testabilité dun assemblage de composants. Deux configurations ont été identifiées sur un diagramme de classes, comme pouvant générer des relations entre objets difficiles à tester, et sont appelées des anti-patterns de testabilité. Nous avons donc défini un critère de test pour couvrir ces anti-patterns, et proposé une mesure de complexité associée. Cette mesure, qui est évaluée sur un diagramme de classes, correspond à la testabilité dun assemblage. En effet, cette mesure permet dévaluer leffort nécessaire pour vérifier le critère de test sur un assemblage.
Comme la mesure est évaluée sur une spécification du programme, et pas sur le programme réel, la testabilité obtenue est une valeur prédictive « au pire », i.e., qui sera effective si toutes les relations entre classes détectées deviennent des relations entre objets. Il apparaît alors quune façon daméliorer la testabilité est dajouter de linformation sur le diagramme pour se prémunir contre une implantation dure à tester. La seconde contribution de ce chapitre consiste à préciser cette information à laide de trois stéréotypes spécifiques qui détaillent le rôle de la dépendance d'un point de vue test (creation only, use consult only ou use definition), dans la logique du test classique de flux de données.
Au cours de la seconde partie de cette étude nous nous sommes concentrés sur les design patterns comme des sous-ensembles logiques dans un diagramme global. En effet, les problèmes de testabilité à un niveau global impliquent souvent un grand nombre de classes et d'interactions et peuvent être ainsi très difficiles à résoudre. Il semblait donc intéressant de trouver un niveau intermédiaire pour améliorer la testabilité. L'analyse de testabilité appliquée sur des instances de design patterns a montré que les problèmes pourraient être résolus à la main ou bien paramétrés grâce aux diagrammes de collaboration (une solution de types transformation de modèles). Comme résultat de l'étude des design patterns, nous avons proposé une grille contenant le nombre d'anti-patterns pouvant apparaître si aucune spécification supplémentaire n'est ajoutée sur les liens d'association du diagramme de classes.Conclusions et perspectives
Au cours de cette thèse, nous nous sommes intéressés à certains mécanismes et constructions particuliers du paradigme objet pour proposer des solutions adaptées au test de ces programmes. Les différentes études décrites dans ce document se sont inscrites dans le cadre dune méthodologie originale pour la conception de composants logiciels fiables. Cette méthodologie considère le test comme le moyen principal pour la validation dun programme. Elle impose donc de prendre en compte lactivité de test tout au long des phases danalyse, de conception et dimplantation. Les travaux présentés dans ce document se sont articulés autour de trois points principaux se rapportant à trois aspects différents du développement dun composant.
Contributions majeures
Les trois contributions majeures de cette thèse sarticulent autour de deux axes complémentaires : la conception testable de systèmes orientés objet par assemblage de composants et la validation des composants et assemblages. Les deux sections suivantes résument ces contributions.
Validation de composants
Les chapitres REF _Ref37930413 \r \h 3 et REF _Ref37930417 \r \h 4 se sont intéressés à la validation de composants et dassemblages dans le cadre de la méthode des composants autotestables. Nous avons tout dabord étudié loptimisation des cas de test pour un composant, puis la conception par contrat pour la validation dassemblages de composants.
Au cours du chapitre REF _Ref37930458 \r \h 3 nous avons proposé une solution pour le problème de la génération et de loptimisation automatique dun ensemble de cas de test pour un composant. Nous avons étudié un algorithme génétique pour résoudre ce problème. Lanalyse des résultats expérimentaux plutôt décevants nous a conduit à proposer une adaptation de lalgorithme que nous appelons un algorithme bactériologique. Le développement dun outil nous a permis deffectuer plusieurs expériences qui ont confirmé la pertinence de lapproche pour le test dun composant.
Le chapitre REF _Ref37930439 \r \h 4 propose une étude de la conception par contrat pour le test dun système OO. Nous avons analysé lapport de cette méthode pour deux aspects du test : la détection et la localisation des erreurs. Pour cela, nous avons défini respectivement, les mesures de robustesse et de diagnosabilité pour des systèmes OO conçus par contrat. Ceci nous a ensuite permis destimer limpact de la densité et de la qualité des contrats sur ces deux mesures. Les résultats ont montré que la simple introduction de contrats réduit rapidement leffort de détection et de localisation des erreurs, et quau-delà dune densité minimale, la qualité des contrats (leur capacité à détecter un état erroné du système) est le facteur le plus important pour renforcer la robustesse et faciliter le diagnostic.
Figure SEQ Figure \* ARABIC 82 Processus global pour la conception dun composant fiable
A partir de ces deux études, nous proposons une approche itérative de la conception de composants autotestables. Cette approche consiste à améliorer les trois facettes du composant autotestable, en six étapes ( REF _Ref31024496 \h Figure 82) :
une version initiale de limplantation, des contrats et des cas de test est disponible
une analyse de mutation permet daméliorer les cas de test. Nous avons étudié deux algorithmes évolutionnistes pour automatiser cette étape.
les cas de test sont exécutés sur limplantation. Si des erreurs sont détectées, elles doivent être localisées et corrigées. Dans ce cas, il faut revenir à létape 1, puisquune modification du programme initial, modifie la génération de mutants, et donc la génération de cas de test.
lefficacité des contrats est mesurée, de nouveau à laide dune analyse de mutation. Cette fois, on nutilise que les contrats comme oracles pour détecter les mutants. Le score de mutation obtenu correspond à la qualité des contrats.
les contrats sont améliorés si nécessaire.
lensemble des cas de test est minimisé, et exécuté sur le composant. Létat du composant est sauvegardé après lexécution de chaque cas de test, et cest ce qui sert doracle global.
Testabilité dun assemblage de composants
La troisième contribution de cette thèse concerne la testabilité des systèmes orientés objet. Cette étude consiste à prendre en compte limpact des structures de contrôle particulières engendrées au cours dun assemblage de composants sur la testabilité des systèmes. En effet, lutilisation de mécanismes tels que la délégation et le polymorphisme entraîne une forte répartition du contrôle à travers tout le système, et implique des interactions complexes entre les objets du système. Nous avons donc défini un critère de test qui vise à couvrir ces interactions. Associée à ce critère, nous proposons une mesure de complexité, calculable à partir dun diagramme de classes et qui permet dévaluer la testabilité dun assemblage dés la conception. Au cours de cette étude de testabilité, nous avons aussi observé le comportement particulier de lapplication des design patterns pour le test. Nous avons montré comment, en forçant une utilisation importante de lhéritage et de la délégation, ils suppriment certains problèmes de testabilité des programmes procéduraux, mais introduisent incidemment dautres difficultés. Or, ces difficultés peuvent être décelées à partir dun diagramme de classes et peuvent être évaluées avec le critère de test proposé.
Perspectives
La génération de test pour le diagnostic
Le diagnostic est létape du test qui consiste à localiser et corriger une erreur lorsquun cas de test a échoué. Limpact de la stratégie de test choisie sur lefficacité du diagnostic est largement reconnue. Cependant, très peu de travaux se sont intéressés à quantifier cet impact. Sur ce point, nous avons commencé une nouvelle étude qui, à partir de lanalyse des algorithmes de diagnostic, définit des critères de test pour une localisation efficace des erreurs dans un programme. Nous utilisons ensuite un algorithme bactériologique pour générer automatiquement des cas de test qui vérifient ces critères. Les expériences devraient nous permettre détudier lefficacité des cas de test générés pour la localisation derreurs, ainsi que la validité du modèle de diagnosabilité proposé dans cette thèse.
Les contrats comme oracle de test
Au cours de cette thèse, nous avons commencé, en collaboration avec une équipe de luniversité de Carleton à Ottawa, à étudier certaines propriétés des contrats qui permettraient dévaluer leur efficacité pour la détection derreur. Lidée initiale était de prioritiser les cas de test qui nécessitent un oracle explicite en présence de contrats. En effet, notre idée était dobserver les traces dexécution des cas de test, et de classer ensuite les cas de test en fonction de la qualité des contrats le long du flot dexécution de chacun. On obtiendrait alors un ensemble de cas de test qui exécutent des parties du système dans lesquelles les contrats sont faibles (détectent peu derreurs), et pour lesquels ils faudrait écrire un oracle explicite. La définition de loracle pour les cas de test exécutant des contrats efficaces pourrait être reportée à plus tard. Faute de temps, ces travaux nont pas pu aboutir, mais cela semble être une perspective intéressante pour la diminution de leffort de test (en facilitant lécriture de la fonction doracle).
Transformation de modèles pour la testabilité
Globalement, et notre travail a tendu vers cela dans un but préventif, il doit être possible daccompagner toutes les étapes de raffinement de lanalyse et de la conception en transformant les modèles de telle sorte quon évite ou limite une dégradation de la testabilité. Un cas particulier de transformation de modèle dont la testabilité peut être contrôlée concerne les design patterns évoqués ci-après. Dans la mesure où dans le contexte du mde (Model Driven Enginnering) on dispose dun langage de transformation, défini au niveau méta, alors cette technique pourrait être appliquée de façon systématique à toute transformation identifiée et cataloguée. Ainsi, lajout de stéréotypes lors de lapplication de design patterns serait vue comme une transformation de modèle, et pourrait être automatisée pour rendre les instances de design patterns testables dans un diagramme de classes.
Design patterns et test
En forçant la distribution du contrôle à travers plusieurs classes, les design patterns rendent plus difficiles lexpression de contrats locaux à une classe. Un point intéressant pour la suite serait de reprendre létude da la rédaction de contrats efficaces dans le cadre dune conception fondée sur les patterns, et dexplorer les points suivants :
existe-t-il des règles spécifiques pour la rédaction de contrats lors de lapplication de design patterns ?
les contrats peuvent-ils être efficaces pour la détection derreur lors de lapplication de design patterns ?
Annexes
Répartition des contrats dans un système OO
Cette annexe présente le détail de la mesure de la répartition des contrats sur cinq systèmes écrits en JAVA. Ces résultats justifient la validité de lhypothèse de répartition uniforme des contrats nécessaire au modèle de diagnosabilité définit au chapitre REF _Ref37828404 \r \h 4.
Ces répartitions ont été obtenues en utilisant loutil JTracor.
Figure SEQ Figure \* ARABIC 83 Répartition des contrats dans une classe List
Figure SEQ Figure \* ARABIC 84 Répartition des contrats dans un serveur de réunion virtuelle
Figure SEQ Figure \* ARABIC 85 Répartition des contrats dans la suite de tests de JUnit
Figure SEQ Figure \* ARABIC 86 Répartition des contrats dans le JDK
Figure SEQ Figure \* ARABIC 87 Répartition des contrats dans Jtree
Interactions dobjets pour le gestionnaire de livres
Nous présentons ici les traces dexécution ainsi que les graphes de relations entre objets (obtenus avec Jtracor) pour les cas de test TC1 et TC3 da la section REF _Ref38018054 \r \h 5.3.4. JTracor est un framework pour tracer lexécution de programmes Java, dont nous donnons ici les résultats sous forme textuelle et sous forme de diagrammes dobjets. Cet outil nous a permis dexhiber deux interactions dobjets, qui rendent limplantation de ce système peu testable daprès notre critère (section REF _Ref43735277 \r \h 5.3). Nous commençons par exhiber une interaction dauto-utilisation effective, puis une interaction dobjets. Enfin, nous donnons les résultats de JTracor sur un autre cas de test qui couvre une interaction plus complexe que les deux premières
AU1
Cette section résume les traces dexécution pour le cas de test TC1, qui teste linteraction dauto-utilisation AU1 de Book vers elle-même à travers BookEvent, dans le gestionnaire de livres.
Cas de test TC1 :
public void testManageEvent(){
Book b = new Book();
b.manageEvent(setDamaged);//the trace is produced from this call
}
Trace textuelle pour TC1 :
| | | | | |->SetDamaged@76.execute
| | | | | | |->Book@82.setDamaged
Diagramme dobjets pour TC1:
AU2
Cette section résume les traces dexécution pour le cas de test TC3, qui teste linteraction de classes AU1 entre BookEvent et Book par deux chemins différents (un direct et un autre passant à travers BookState), dans le gestionnaire de livres.
Cas de test :
public void testManageEvent(){
Book b = new Book(); //the book is in the ordered state
b.manageEvent(deliver); //puts the book in the available state
b.manageEvent(borrow); //the trace is produced from this call
}
Trace textuelle pour TC3:
| | | | | |->Borrow@72.execute
| | | | | | |->Book@82.getCurrent_state
| | | | | | |->Available@81.borrow
| | | | | | | |->Borrowed@86.
| | | | | | | | |->Book_State@86.
| | | | | | | |->Book@82.setCurrent_state
Diagrammes dobjets pour TC3:
IC(BookEvent, Book)
Nous détaillons ici un cas de test plus complexe qui couvre linteraction de classes entre BookEvent et Book : un des chemins passe à travers Available, Reserved et BookState.
Cas de test :
public void testManageEvent(){
Book b = new Book(); //the book is in the ordered state
b.manageEvent(reserve); // the trace is produced from this call
}
Trace textuelle :
| | | | | |->Reserve@66.execute
| | | | | | |->Book@82.getCurrent_state
| | | | | | |->Available@81.reserve
| | | | | | | |->Reserved@87.
| | | | | | | | |->InLibrary@87.
| | | | | | | | | |->Book_State@87.
| | | | | | | |->Book@82.setCurrent_state
| | | | | | | |->Book_State@81.reserve
| | | | | | | | |->Book@82.inc_nb_res
Diagramme dobjets :
Glossaire
Algorithme évolutionniste : extension dun algorithme génétique pour inclure les systèmes de classification et la programmation génétique où chaque solution est un programme.
Algorithme génétique : modèle computationnel de la résolution de problème fondé sur les principes de la génétique et les mécanismes de la génétique.
Assertion : prédicat ou combinaison de prédicats.
Cas de test : procédure qui permet dexécuter le programme sous test avec une donnée de test.
Client : un composant Ci est client dun composant Cj sil utilise les services de Cj (i.e. si Ci a un attribut, une variable locale ou un paramètre de méthode de type Cj).
Composant autotestable : composant logiciel dans lequel sont regroupés son implantation, une spécification sous forme de contrats exécutables, et un ensemble de cas de test.
Conception par contrat : méthodologie de conception pour des systèmes orientés objet qui consiste à définir qui consiste à définir les devoirs et obligations des éléments du système. Trois types principaux dassertions peuvent être définis : des pré et post conditions sont pour les méthodes, et un invariant pour les classes. La précondition définit la condition légale pour utiliser une méthode. La postcondition définit les propriétés du résultat fourni par la méthode. Un invariant définit des propriétés qui doivent vérifiées létat de lobjet avant et après chaque appel de méthode.
Couplage : mesure la force de la relation entre deux modules, i.e. des classes dans le cas des modèles orientés objet.
Critère de test : critère qui doit être vérifié par des cas de test sur un programme sous test. Par exemple, couverture des chemins, couverture des prédicats, score de mutation
Défaillance: événement survenant lorsque le service délivré dévie de laccomplissement de la fonction du système.
Design Pattern : un design pattern représente une solution aux problèmes qui surgissent quand un logiciel est développé dans un contexte particulier. Les design patterns peuvent être considérés en tant que microarchitectures réutilisables qui contribuent à une architecture du système globale; ils saisissent les structures dynamiques et statiques et les collaborations entre les principaux participants du modèle de conception.
Délégation : mécanisme de programmation objet qui consiste, pour un objet, à déléguer tout ou partie du traitement dune méthode à un autre objet.
Diagnosabilité : mesure de leffort pour le diagnostic dun programme.
Diagnostic : localisation et correction dune faute lorsquun cas de test a échoué.
Donnée de test : donnée en entrée pour exécuter un programme sous test.
Double renvoi : double mécanisme de délégation.
Driver de test : programme qui exécute les cas de test sur le programme à tester, et qui sauvegarde les verdicts pour chaque cas de test.
Erreur: partie de létat dun système qui est susceptible dentraîner une défaillance.
Faute: cause adjugée ou supposée dune erreur.
Framework : Un framework est un ensemble de classes et de collaborations entre les instances de ces classes
.
OCL : Object Constraint Language. Langage dassertions pour lexpression de contraintes sur un modèle UML.
Oracle : une fonction doracle calcule la valeur attendue comme résultat dun programme en fonction des données en entrée.
Orienté objets (conception) : la conception objet a pour but de préparer limplantation dun programme dans un langage orienté objet.
Polymorphisme : le fait quune référence puisse pointer sur des objets de types différents au cours de lexécution.
Robustesse : La robustesse dun composant est définie comme la capacité de ce composant à fonctionner même dans des conditions anormales. La robustesse peut aussi être vue comme la probabilité quune erreur soit détectée et traitée par un mécanisme dexception sachant quune défaillance se produit certainement sinon.
Testabilité : La testabilité est un facteur de qualité, défini comme la facilité à tester un logiciel. La testabilité se dérive en trois attributs :
le coût global de test : le coût global pour obtenir des cas de test vérifiant un critère de test donné
la contrôlabilité : la facilité pour générer des données de test efficaces (propriété intrinsèque au logiciel sous test)
lobservabilité : la facilité avec laquelle il est possible de vérifier la pertinence des résultats obtenus après lexécution des cas de test
Test de logiciel : il existe deux grandes catégories de test logiciel :
le test dynamique qui consiste à exécuter des cas de test sur un programme et vérifier si la valeur obtenue est conforme à la valeur attendue.
le test statique qui observe les propriétés statiques dun programme pour détecter des fautes.
UML : Unified Modelling Language. Langage graphique pour la conception de logiciels, en particulier des logiciels orientés objet.
Verdict de test : un cas de test passe si la valeur obtenue après exécution du cas de test est conforme à loracle, sinon il échoue.
Bibliographie
ADDIN EN.REFLIST [Abdurazik''00] A. Abdurazik and A. J. Offutt, "Using UML Collaboration Diagrams for Static Checking and Test Generation". In Proceedings of UML'00, York, UK. pp. 383 - 395, October 2000.
[AGEDIS''00] AGEDIS, "Automated Generation and Execution of Test Suites for DIstributed Component-based Software".
HYPERLINK "http://www.agedis.de/index.shtml" http://www.agedis.de/index.shtml.
[Agerbo''98] E. Agerbo and A. Cornils, "How to Preserve the Benefits of Design Patterns". In Proceedings of OOPSLA'98, Vancouver, BC, Canada. pp. 134 - 143, October 1998.
[Agrawal''95] H. Agrawal, J. Horgan, S. London and W. Wong, "Fault Localization using Execution Slices and Dataflow Tests". In Proceedings of ISSRE'95 (Int. Symposium on Software Reliability Engineering), Toulouse, France. pp. 143 - 151, October 1995.
[Alexander''00] R. T. Alexander and J. Offutt, "Criteria for Testing Polymorphic Relationships". In Proceedings of ISSRE'00 (Int. Symposium on Software Reliability Engineering), San Jose, CA, USA. pp. 15 - 23, October 2000.
[Alexander''02a] R. T. Alexander, A. J. Offutt and J. M. Bieman, "Fault Detection Capabilities of Coupling-Based OO Testing". In Proceedings of ISSRE'02 (Int. Symposium on Software Reliability Engineering), Annapolis, MD, USA. pp. 207 - 218, November 2002.
[Alexander''02b] R. T. Alexander, J. M. Bieman, G. Sudipto and J. Bixia, "Mutation of Java Objects". In Proceedings of ISSRE'02 (Int. Symposium on Software Reliability Engineering), Annapolis, MD, USA. pp. 341 - 351, November 2002.
[Antoniol''02] G. Antoniol, L. Briand, M. DiPenta and Y. Labiche, "A case Study Using The Round-Trip Strategy for State-Based Class Testing". In Proceedings of ISSRE'02 (Int. Symposium on Software Reliability Engineering), Annapolis, MD, USA. pp. 269 - 279, November 2002.
[Baudry''00a] B. Baudry, Y. Le Traon and V. L. Hanh, "Testing-for-Trust: the Genetic Selection Model applied to Component Qualification". In Proceedings of TOOLS Europe (Technology of object-oriented languages and systems), Mont St Michel, France. pp. 108 - 119, June 2000.
[Baudry''00b] B. Baudry, Y. Le Traon, V. L. Hanh and J.-M. Jézéquel, "Building Trust into OO Components using a Genetic Analogy". In Proceedings of ISSRE'00 (Int. Symposium on Software Reliability Engineering), San Jose, CA, USA. pp. 4 - 14, October 2000.
[Baudry''00c] B. Baudry, Y. Le Traon, J.-M. Jézéquel and V. L. Hanh, "Trustable Components: Yet Another Mutation-Based Approach". In Proceedings of 1st Symposium on Mutation Testing, San Jose, CA. pp. 69 - 76, October 2000.
[Baudry''01a] B. Baudry, Y. Le Traon and J.-M. Jézéquel, "Robustness and Diagnosability of Designed by Contracts OO Systems". In Proceedings of Metrics'01 (Software Metrics Symposium), London, UK. pp. 272 - 283, April 2001.
[Baudry''01b] B. Baudry, Y. Le Traon, G. Sunyé and J.-M. Jézéquel, "Towards a Safe Use of Design Patterns for OO Software Testability". In Proceedings of ISSRE'01 (Int. Symposium on Software Reliability Engineering), Hong-Kong, China. pp. 324 - 329, November 2001.
[Baudry''02a] B. Baudry, Y. Le Traon and G. Sunyé, "Testability Analysis of UML Class Diagram". In Proceedings of Metrics'02 (Software Metrics Symposium), Ottawa, Canada. pp. 54 - 63, June 2002.
[Baudry''02b] B. Baudry, F. Fleurey, Y. Le Traon and J.-M. Jézéquel, "Automatic Test Cases Optimization using a Bacteriological Adaptation Model: Application to .NET Components". In Proceedings of ASE'02 (Automated Software Engineering), Edimburgh, Scotland, UK. pp. 253 - 256, September 2002.
[Baudry''02c] B. Baudry, F. Fleurey, Y. Le Traon and J.-M. Jézéquel, "Computational Intelligence for Testing .NET Components". In Proceedings of Microsoft Summer Research Workshop, Cambrige, UK. pp. September 2002.
[Baudry''02d] B. Baudry, F. Fleurey, Y. Le Traon and J.-M. Jézéquel, "Genes and Bacteria for Automatic Test Cases Optimization in the .NET Environment". In Proceedings of ISSRE'02 (Int. Symposium on Software Reliability Engineering), Annapolis, MD, USA. pp. 195 - 206, November 2002.
[Baudry''03] B. Baudry, Y. Le Traon, G. Sunyé and J.-M. Jézéquel, "Measuring and Improving Design Patterns Testability". In Proceedings of Metrics'03 (Software Metrics Symposium), Sydney, Australia. pp. September 2003.
[Beck''99] K. Beck, "Extreme programming explained". Addison-Wesley 1999.
[Beck''01] K. Beck and E. Gamma, "JUnit".
HYPERLINK "http://www.junit.org/index.htm" http://www.junit.org/index.htm.
[Beizer''90] B. Beizer, "Software Testing Techniques". Van Norstrand Reinhold 1990.
[Bieman''89] J. M. Bieman and J. Schultz, "Estimating the Number of Test Cases Required to Satisfy the all-du-paths Testing Criterion". In Proceedings of Software Testing Analysis and Verification Sympoium pp. 179 - 186, December 1989.
[Binder''94] R. V. Binder, "Design for Testability in Object-Oriented systems". Communications of the ACM 37(9): 87 - 101 September 1994.
[Binder''99] R. V. Binder, "Testing Object-Oriented Systems: Models, Patterns and Tools". Addison-Wesley 1999.
[Boden''96] E. B. Boden and G. F. Martino, "Testing software using order-based genetic algorithms". In Proceedings of Genetic Programming 1996 Conference, Stanford, CA, USA. pp. 461 - 466, July 1996.
[Bogdanov''01] K. Bogdanov and M. Holcombe, "Statechart Testing Method for Aircraft Control Systems". Software Testing, Verification and Reliability 11(1): 39 - 54 March 2001.
[Booch''99] G. Booch, J. Rumbaugh and I. Jacobson, "The Unified Modeling Language User Guide". Addison-Wesley object technology series 1999.
[Briand''96] L. Briand, S. Morasca and V. S. Basili, "Property-based Software Engineering Measurement". IEEE Transactions on Software Enginnering 22(1): 68 - 86 January 1996.
[Briand''99] L. Briand, J. W. Daly and J. K. Wüst, "A Unified Framework for Coupling Measurement in Object-Oriented Systems". IEEE Transactions on Software Enginnering 25(1): 91 - 121 January/February 1999.
[Briand''01] L. Briand and Y. Labiche, "Revisiting Strategies for Ordering Class Integration Testing in the Presence of Dependency Cycles". In Proceedings of ISSRE'01 (Int. Symposium on Software Reliability Engineering), Hong-Kong, China. pp. 287 - 296, December 2001.
[Briand''02a] L. Briand and Y. Labiche, "A UML-based approach to System Testing". Journal of Software and Systems Modeling 1(1): 10 - 42 September 2002.
[Briand''02b] L. Briand, Y. Labiche and H. Sun, "Investigating the use of analysis contracts to support fault isolation in object oriented code". In Proceedings of International Symposium on Software Testing and Analysis, Roma, Italy. pp. 70 - 80, June 2002.
[Brown''98] W. J. Brown, R. C. Malveau, W. H. Brown, H. W. McCormick, III and T. J. Mowbray, "AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis". John Wiley & Sons 1998.
[Buy''00] U. Buy, A. Orso and M. Pezzè, "Automated Testing of Classes". In Proceedings of ISSTA'00, Portland, OR, USA. pp. 39 - 48, August 2000.
[Carrillo-Castellon''96] M. Carrillo-Castellon, J. Garcia-Molina, E. Pimentel and I. Repiso, "Design by contract in Smalltalk". Journal of Object-Oriented Programming 8(7): 23 - 38 November 1996.
[Chen''99] M.-H. Chen and H. M. Kao, "Testing object-oriented programs - an integrated approach". In Proceedings of ISSRE'99 (Int. Symposium on Software Reliability Engineering), Boca Raton, FL, USA. pp. 73 - 82, November 1999.
[Cheon''02] Y. Cheon and G. T. Leavens, "A Simple and Practical Approach to Unit Testing: The JML and JUnit Way". In Proceedings of ECOOP, Malaga, Spain. pp. 231 - 255, June 2002.
[Chevalley''01a] P. Chevalley and P. Thévenod-Fosse, "Automated generation of statistical test cases from UML state diagrams". In Proceedings of COMPSAC (25th Annual International Computer Software and Applications Conference), Chicago, IL, USA. pp. 205 - 214, October 2001.
[Chevalley''01b] P. Chevalley and P. Thevenod-Fosse, "An Empirical Evaluation of Statistical Testing Designed from UML State Diagrams: the Flight Guidance System Case Study". In Proceedings of ISSRE'01 (Int. Symposium on Software Reliability Engineering), Hong Kong, China. pp. 27 - 30, November 2001.
[Chevalley''01c] P. Chevalley, "Applying Mutation Analysis for Object-Oriented Programs Using a Reflective Approach". In Proceedings of Eighth Asia-Pacific Software Engineering Conference, Macao, China. pp. 267 - 70, December 2001.
[Chidamber''94] S. R. Chidamber and C. F. Kemerer, "A Metrics Suite for Object Oriented Design". IEEE Transactions on Software Enginnering 20(6): 476 - 493 June 1994.
[Clarke''76] L. A. Clarke, "A system to generate test data and symbolically execute programs". IEEE Transactions on Software Enginnering 2(3): 215 - 22 September 1976.
[Collet''96] P. Collet and R. Rousseau, "Assertions are Objects too!" In Proceedings of First Intern. Conf. on Object-Oriented Technology, St Petersburgh, Russia. pp. June 1996.
[Correa''00] A. Correa, C. M. L. Werner and G. Zaverucha, "Object Oriented Design Expertise Reuse: An Approach Based on Heuristics, Design Patterns and Anti-patterns". In Proceedings of International Conference on Software Reuse pp. 336 - 352, June 2000.
[Craig''02] P. Craig, "NUnit".
HYPERLINK "http://nunit.sourceforge.net/" http://nunit.sourceforge.net/.
[de Icaza''01] M. de Icaza, "mono : a C# compiler for Linux."
HYPERLINK "http://www.go-mono.com/index.html" http://www.go-mono.com/index.html.
[DeMillo''78] R. DeMillo, R. Lipton and F. Sayward, "Hints on Test Data Selection : Help For The Practicing Programmer". IEEE Computer 11(4): 34 - 41 April 1978.
[DeMillo''91] R. DeMillo and A. J. Offutt, "Constraint-Based Automatic Test Data Generation". IEEE Transactions on Software Enginnering 17(9): 900 - 910 September 1991.
[DeMillo''93] R. DeMillo and A. J. Offutt, "Experimental results from an automatic test case generator". ACM Transactions on Software Engineering and Methodology 2(2): 109 - 27 April 1993.
[Deveaux''99a] D. Deveaux, J.-M. Jézéquel and Y. Le Traon, "Self-testable Components: from Pragmatic Tests to a Design-for-Testability Methodology". In Proceedings of TOOLS'99 (Technology of Object Oriented Languages and Systems), Nancy, France. pp. 96 - 107, June 1999.
[Deveaux''99b] D. Deveaux, J.-M. Jézéquel and Y. Le Traon, "Self-testable components: from pragmatic tests to design-for-testability methodology." In Proceedings of Technology of Object Oriented Languages and Systems (TOOLS'99), Nancy, France. pp. 96-107, June 1999.
[Deveaux''01] D. Deveaux and Y. Le Traon, "XML to Manage Source Code Engineering in Object-Oriented Development: an Example." In Proceedings of XSE01 workshop at ICSE'2001, Toronto, Canada. pp. 28 - 31, May 2001.
[Dick''93] J. Dick and A. Faivre, "Automating the Generation and Sequencing of Test Cases from Model-based Specifications". In Proceedings of FME'93 pp. 268 - 284, April 1993.
[D'Souza''98] D. F. D'Souza and A. C. Wills, "Object, Components and Frameworks with UML, The Catalysis Approach". Object Technology, Addison-Wesley 1998.
[Dustin''99] E. Dustin, J. Rashka and J. Paul, "Automated Software Testing". Addison-Wesley 1999.
[Eden''99] A. H. Eden "Precise Specification of Design Patterns and Tool Support in their Application". University of Tel Aviv, 1999.
[Edwards''01] S. H. Edwards, "A Framework for Practical, Automated Balck-Box Testing of Component-Based Software". Software Testing, Verification and Reliability 11(2): 97 - 111 June 2001.
[Fenkam''02] P. Fenkam, H. Gall and M. Jazayeri, "Constructing Corba-Supported Oracles for Testing: A Case Study in Automated Software Testing". In Proceedings of ASE'02 (Automated Software Engineering), Edimburgh, UK. pp. 129 - 138, September 2002.
[Fenton''86] N. E. Fenton and R. W. Whitty, "Axiomatic Approach to Software Metrication through Program Decomposition". Computer Journal 29(4): 330 - 339 August 1986.
[Fewster''99] M. Fewster and D. Graham, "Software Test Automation". Addison-Wesley 1999.
[Findler''01] R. B. Findler and M. Felleisen, "Contract Soudness for Object-Oriented Languages". In Proceedings of OOPSLA 2001, Tampa Bay, FL, USA. pp. 1 - 15, October 2001.
[Fowler''97] M. Fowler, K. Scott and G. Booch, "UML distilled". Object Oriented series, Addison-Wesley 1997.
[Fowler''01] M. Fowler, "Reducing Coupling". IEEE Software 18(4): 102 - 104 July-August 2001.
[Freedman''91] R. S. Freedman, "Testability of Software Components". IEEE Transactions on Software Enginnering 17(6): 553 - 564 June 1991.
[Gamma''95] E. Gamma, R. Helm, R. Johnson and J. Vlissides, "Design Patterns: Elements of Reusable Object-Oriented Software". Professional Computing, Addison-Wesley 1995.
[Ghosh''00] S. Ghosh and A. Mathur, "Interface Mutation to assess the adequacy of tests for components and systems". In Proceedings of TOOLS, Santa Barbara, CA, USA. pp. 37 - 46, August 2000.
[Goldberg''89] D. E. Goldberg, "Genetic Algorithms in Search, Optimization and Machine Learning". Addison-Wesley 1989.
[Grieskamp''02] W. Grieskamp, Y. Gurevich, W. Schulte and M. Veanes, "Generating Finite State Machines from Abstract State Machines". In Proceedings of ISSTA'02 (International Symposium on Software Testing and Analysis), Rome, Italy. pp. 112-122, July 2002.
[Hanh''02] V. L. Hanh "Test et modèle UML : stratégie, plan et synthèse de test". Université de Rennes 1, 2002.
[Harrold''94] M. J. Harrold and G. Rothermel, "Performing Data Flow Testing on Classes". In Proceedings of FSE (Foundation on Software Engineering), New Orleans, US. pp. 154 - 163, December 1994.
[Ho''99] W.-M. Ho, J.-M. Jézéquel, A. Le Guennec and F. Pennaneac'h, "UMLAUT: an Extendible UML Transformation Framework". In Proceedings of ASE'99 (Automated Software Engineering), Cocoa Beach, Florida, USA. pp. 275 - 278, October 1999.
[Hoijin''98] Y. Hoijin, C. Byoungju and J. Jin-Ok, "Mutation-based inter-class testing". In Proceedings of Asia Pacific Software Engineering Conference, Taipei, Taiwan. pp. 174 - 181, December 1998.
[Holland''74] J. H. Holland, "Adaptation in Natural and Artificial Systems". University of Michigan Press 1974.
[Howden''70] W. E. Howden and Y. Huang, "Software Trustability". In Proceedings of IEEE Symposium on Adaptive processes- Decision and Control pp. 1970.
[Jéron''98] T. Jéron and P. Morel, "Test Generation Derived from Model-checking". In Proceedings of CAV'99, Kyoto, Japan. pp. 108 - 122, July 1998.
[Jézéquel''97] J.-M. Jézéquel and B. Meyer, "Design by Contract: The lessons of Ariane". Computer 30(1): 129 - 130 January 1997.
[Jézéquel''99] J.-M. Jézéquel, M. Train and C. Mingins, "Design Patterns and Contracts". Addison-Wesley 1999.
[Jézéquel''01] J.-M. Jézéquel, D. Deveaux and Y. Le Traon, "Reliable Objects: a Lightweight Approach Applied to Java". IEEE Software 18(4): 76 - 83 July/August 2001.
[Jones''96] B. F. Jones, H.-H. Sthamer and D. E. Eyres, "Automatic Structural Testing Using Genetic Algorithms". Software Engineering Journal 11(5): 299 - 306 September 1996.
[Jones''98] B. F. Jones, D. E. Eyres and H.-H. Sthamer, "A Strategy for using Genetic Algorithms to Automate Branch and Fault-based Testing". The Computer Journal 41(2): 98 - 107 1998.
[Jones''02] J. A. Jones, M. J. Harrold and J. Stasko, "Visualization of Test Information to Assist Fault Localization". In Proceedings of ICSE'02, Orlando, FL, USA. pp. 467 - 477, May 2002.
[Kamkar''95] M. Kamkar, "An Overview and Comparative Classification of Program Slicing Techniques". Journal of Systems and Software 31(3): 197 - 214 December 1995.
[Kim''01] S.-W. Kim, J. A. Clark and J. A. McDermid, "Investigating the effectiveness of object-oriented testing strategies using the mutation method". Software Testing, Verification and Reliability 11(4): 207 - 225 December 2001.
[Kim''99] Y. G. Kim, H. S. Hong, D. H. Bae and S. D. Cha, "Test cases generation from UML state diagrams". IEEE Proceedings-Software 146(4): 187 - 192 August 1999.
[Kitchenham''95] B. Kitchenham, S. L. Pfleeger and N. Fenton, "Towards a framework for software measurement validation". IEEE Transactions on Software Enginnering 21(12): 929 - 944 December 1995.
[Korel''90] B. Korel, "Automated Software Test Data Generation". IEEE Transactions on Software Enginnering 16(8): 870 - 879 August 1990.
[Korel''92] B. Korel, "Dynamic method for software test data generation". Software Testing, Verification and Reliability 2(4): 203 - 213 December 1992.
[Korel''96] B. Korel and A. M. Al-Yami, "Assertion-oriented automated test data generation". In Proceedings of ICSE, Berlin, Germany. pp. 71 - 80, March 1996.
[Korel''97] B. Korel, "Computation Of Dynamic Program Slices For Unstructured Programs". IEEE Transactions on Software Enginnering 23(1): 17 - 34 January 1997.
[Kung''96] D. C. Kung, J. Gao, P. Hsia, Y. Toyashima and C. Chen, "On Regression Testing of Object-Oriented Programs". The Journal of Systems and Software 32(1): 21 - 40 January 1996.
[Labiche''00] Y. Labiche, P. Thevenod-Fosse, H. Waeselynck and M.-H. Durand, "Testing levels for object-oriented software". In Proceedings of ICSE, Limerick, Ireland. pp. June 2000.
[Laprie''95] J.-C. Laprie, "Guide de la surete de fonctionnement". Cepadues 1995.
[Le Guennec''00] A. Le Guennec, G. Sunyé and J.-M. Jézéquel, "Precise Modeling of Design Patterns". In Proceedings of UML'00 pp. 482 - 496, October 2000.
[Le Traon''97] Y. Le Traon and C. Robach, "Testability Measurements for Data Flow Designs". In Proceedings of International Software Metrics Symposium (Metrics'97), Albuquerque, NM, USA. pp. 91 - 98, November 1997.
[Le Traon''98] Y. Le Traon, F. Ouabdessalam and C. Robach, "Software Diagnosability". In Proceedings of ISSRE'98 (Int. Symposium on Software Reliability Engineering), Paderborn, Germany. pp. 257 - 266, November 1998.
[Le Traon''00a] Y. Le Traon, T. Jéron, J.-M. Jézéquel and P. Morel, "Efficient OO Integration and Regression Testing". IEEE Transactions on Reliability 49(1): 12 - 25 March 2000.
[Le Traon''00b] Y. Le Traon, F. Ouabdessalam and C. Robach, "Analyzing Testability on Data Flow Designs". In Proceedings of ISSRE'00 (Int. Symposium on Software Reliability Engineering), San Jose, CA, USA. pp. 162 - 173, October 2000.
[Le Traon''03] Y. Le Traon, F. Ouabdessalam, C. Robach and B. Baudry, "From Diagnosis to Diagnosability: Axiomatization, Measurement and Application". Journal of Systems and Software 65(1): 31 - 50 January 2003.
[Legeard''02] B. Legeard, F. Peureux and M. Utting, "Automated Boundary Testing from Z and B". In Proceedings of Formal Methods (FME'02), Copenhagen, Denmark. pp. 21 - 40, July 2002.
[Liskov''86] B. Liskov and J. Guttag, "Abstraction and Specification in Program Development". MIT Press/Mc Graw Hill 1986.
[Lugato''02] D. Lugato, C. Bigot and Y. Valot, "Validation and automaitc test generation on UML models: the AGATHA approach". Electronic Notes in Theoretical Computer Science 66(2) December 2002.
[Ma''02] Y.-S. Ma, Y.-R. Kwon and A. J. Offutt, "Inter-Class Mutation Operators". In Proceedings of ISSRE'02 (Int. Symposium on Software Reliability Engineering), Annapolis, MD, USA. pp. 352 - 363, November 2002.
[Marinov''01] D. Marinov and S. Khurshid, "TestEra: A Novel Framework for Automated Testing of Java Programs". In Proceedings of ASE'01 (Automated Software Engineering), San Diego, CA, USA. pp. 22 - 31, November 2001.
[Martena''02] V. Martena, A. Orso and M. Pezzè, "Interclass Testing of Object Oriented Software". In Proceedings of International Conference on Engineering of Complex Computer Systems pp. 2002.
[McGregor''99] J. D. McGregor, "Test Patterns: Please Stand By". Journal of Object Oriented Programming 12(3): 14 - 19 June 1999.
[McGregor''01] J. D. McGregor, "A Practical Guide To Testing Object Oriented Testing". Object Technology Series, Addison Wesley 2001.
[McKim''95] J. McKim, "Class Interface Design and Programming by Contract". In Proceedings of TOOLS 17 (Technology of Object Oriented Languages and Systems), Englewood Cliffs. pp. 395 - 419, 1995.
[Meyer''92a] B. Meyer, "Object-oriented software construction". Prentice Hall 1992.
[Meyer''92b] B. Meyer, "Applying Design by Contract". IEEE Computer 25(10): 40 - 51 October 1992.
[Michael''01] C. C. Michael, G. McGraw and M. A. Schatz, "Generating Software Test Data by Evolution". IEEE Transaction on Software Engineering 27(12): 1085 - 1110 December 2001.
[Miller''76] W. Miller and D. L. Spooner, "Automatic Generation of Floating-Point Test Data". IEEE Transactions on Software Enginnering 2(3): 223 - 226 September 1976.
[Mitchell''99] R. Mitchell and J. McKim, "Extending a method of devising software contracts". In Proceedings of Tools'32 pp. 1999.
[MSDN''02a] MSDN, "C# Introduction and Overview".
HYPERLINK "http://msdn.microsoft.com/vstudio/techinfo/articles/upgrade/Csharpintro.asp" http://msdn.microsoft.com/vstudio/techinfo/articles/upgrade/Csharpintro.asp.
[MSDN''02b] MSDN, ".NET homepage".
HYPERLINK "http://msdn.microsoft.com/library/default.asp?url=/nhp/Default.asp?contentid=28000519" http://msdn.microsoft.com/library/default.asp?url=/nhp/Default.asp?contentid=28000519.
[Murray''98] L. Murray, D. Carrington, I. MacColl, J. McDonald and P. Strooper, "Formal Derivation of Finite State Machines for Class Testing". In Proceedings of ZUM '98: The {Z} Formal Specification Notation, Berlin, Germany. pp. 42-59, September 1998.
[Nebut''02] C. Nebut, S. Pickin, Y. Le Traon and J.-M. Jézéquel, "Reusable Test Requirements for UML-Modeled Product Lines". In Proceedings of REPL, Essen, Germany. pp. 51 - 56, September 2002.
[Nordby''02] J. E. Nordby, M. Blom and A. Brunstrom, "On the Relation between Design Contracts and Errors: a Software Development Strategy". In Proceedings of ECBS, Lund, Sweden. pp. April 2002.
[Offutt''92] A. J. Offutt, "Investigations of the software testing coupling effect". ACM Transactions on Software Engineering and Methodology 1(1): 5 - 20 January 1992.
[Offutt''96a] A. J. Offutt, J. Pan, K. Tewary and T. Zhang, "An experimental evaluation of data flow and mutation testing". Software Practice and Experience 26(2) February 1996.
[Offutt''96b] A. J. Offutt, A. Lee, G. Rothermel, R. H. Untch and C. Zapf, "An Experimental Determination of Sufficient Mutant Operators". ACM Transactions on Software Engineering and Methodology 5(2): 99 - 118 April 1996.
[Offutt''97] A. J. Offutt and J. Pan, "Automatically Detecting Equivalent Mutants and Infeasible Paths". The Journal of Software Testing, Verification and Reliability 7(3): 165 - 192 September 1997.
[Offutt''99] A. J. Offutt and A. Abdurazik, "Generating Tests from UML Specifications". In Proceedings of UML'99, Fort Collins, CO, USA. pp. 416 - 429, October 1999.
[OMG''97] OMG, "Object Constraint Language Specification".
HYPERLINK "http://www.omg.org/docs/ad/97-08-08.pdf" http://www.omg.org/docs/ad/97-08-08.pdf.
[Opdyke''92] W. F. Opdyke "Refactoring object-oriented frameworks". University of Illinois, 1992.
[Pargas''99] R. Pargas, M. J. Harrold and R. Peck, "Test-Data Generation Using Genetic Algorithms". Journal of Software Testing, Verifications, and Reliability 9: 263 - 283 September 1999.
[Pickin''02] S. Pickin, C. Jard, Y. Le Traon, T. Jéron, J.-M. Jézéquel and A. Le Guennec, "System Test Synthesis from UML Models of Distributed Software". In Proceedings of FORTE pp. 2002.
[Rapps''85] S. Rapps and E. J. Weyuker, "Selecting Software Test Data Using Data Flow Information". IEEE Transactions on Software Enginnering 11(4): 367 - 375 April 1985.
[Richner''99] T. Richner and S. Ducasse, "Recovering high-level views of object-oriented applications from static and dynamic information". In Proceedings of ICSM'99 (International Conference on Software Maintenance ), Oxford, UK. pp. 13 - 22, September 1999.
[Rosenblum''95] D. S. Rosenblum, "A Practical Approach to Programming With Assertions". IEEE Transactions on Software Enginnering 21(1): 19 - 31 January 1995.
[Rosenzweig''95] M. L. Rosenzweig, "Species Diversity In Space and Time". Cambridge University Press 1995.
[Schultz''93] A. C. Schultz, J. J. Grefenstette and K. A. De Jong, "Test and Evaluation by Genetic Algorithms". IEEE Expert 8(5): 9 - 14 October 1993.
[Shepperd''93] M. Shepperd and D. Ince, "Derivation and Validation of Software Metrics". Oxford University Press 1993.
[Shepperd''98] M. Shepperd, "Object-Oriented Metrics: an Annotated Bibliography".
HYPERLINK "http://dec.bournemouth.ac.uk/ESERG/bibliography.html" http://dec.bournemouth.ac.uk/ESERG/bibliography.html.
[Shyam''94] S. R. Shyam and C. F. Kemerer, "A Metrics Suite for Object Oriented Design". IEEE Transactions on Software Enginnering 20(6): 476 - 493 June 1994.
[Siegel''96] S. Siegel, "Object Oriented Software Testing". John Wiley & Sons 1996.
[Soundarajan''01] N. Soundarajan and B. Tyler, "Specification-based incremental testing of object oriented systems". In Proceedings of TOOLS 39, Santa Barbara, CA, USA. pp. 35 - 44, July 2001.
[SUN''00] SUN, "JavaCC".
HYPERLINK "http://www.webgain.com/products/java_cc/" http://www.webgain.com/products/java_cc/.
[SUN''02] SUN, "JavaTM Platform Debugger Architecture".
HYPERLINK "http://java.sun.com/products/jpda" http://java.sun.com/products/jpda.
[Sunyé''00] G. Sunyé, A. Le Guennec and J.-M. Jézéquel, "Design Pattern Application in UML". In Proceedings of ECOOP'00 pp. 44 - 62, June 2000.
[Szyperski''98] C. Szyperski, "Component Software: Beyond Object-Oriented Programming". ACM Press and Addison Wesley 1998.
[Tahat''01] L. H. Tahat, B. Vaysburg, B. Korel and A. J. Bader, "Requirement-based automated black-box test generation". In Proceedings of COMPSAC, Chicago, IL, USA. pp. 489 - 495, October 2001.
[Tai''99] K. C. Tai and F. J. Daniels, "Inter-Class Test Order for Object-Oriented Software". Journal of Object Oriented Programming 12(4): 18 - 35 July-August 1999.
[Tracey''98] N. Tracey, J. Clark, K. Mander and J. A. McDermid, "An automated framework for structural test-data generation". In Proceedings of ASE, Honolulu, HI, USA. pp. 285 - 288, October 1998.
[Tracey''00] N. Tracey, J. Clark, K. Mander and J. McDermid, "Automated test-data generation for exception conditions". Software Practice and Experience 30(1): 61 - 79 January 2000.
[Tsai''01] W.-T. Tsai, X. Bai, R. Paul and L. Yu, "Scenario-based functional regression testing". In Proceedings of COMPSAC 2001, Chicago, IL, USA. pp. 496 - 501, October 2001.
[Voas''92a] J. M. Voas and K. Miller, "The Revealing Power of a Test Case". Software Testing, Verification and Reliability 2(1): 25 - 42 May 1992.
[Voas''92b] J. M. Voas, "PIE : A Dynamic Failure-Based Technique". IEEE Transactions on Software Enginnering 18(8): 717 - 727 August 1992.
[Voas''93] J. M. Voas and K. Miller, "Semantic Metrics for Software Testability". Journal of Systems and Software 20(3): 207 - 216 March 1993.
[Voas''95] J. M. Voas and K. Miller, "Software Testability: The New Verification". IEEE Software 12(3): 17 - 28 May 1995.
[Voas''99] J. M. Voas and L. Kassab, "Using Assertions to Make Untestable Software More Testable". Software Quality Professional 1(4) September 1999.
[Wegener''01] J. Wegener, A. Baresel and H. Stahmer, "Evolutionary Test Environment for Automatic Structural Testing". Information and Software Technology 43(14): 841 - 854 December 2001.
[Weide''96] B. W. Weide, S. H. Edwards, W. D. Heym, T. J. Long and W. F. Ogden, "Characterizing Observability and Controllability of Software Components". In Proceedings of 4th International Conference on Software Reuse, Orlando, USA. pp. 62 - 71, April 1996.
[Weiser''82] M. Weiser, "Programmers Use Slices When Debugging". Communication of ACM 25(7): 446 - 452 July 1982.
[Weiser''84] M. Weiser, "Program Slicing". IEEE Transactions on Software Enginnering 10(4): 352 - 357 July 1984.
[Wu''03] Y. Wu, M.-H. Chen and A. J. Offutt, "UML-based Integration testing for Component-based Software". In Proceedings of International Conference on COTS-based Software Systems, Ottawa, Canada. pp. February 2003.
[Xanthakis''92] S. Xanthakis, C. Ellis, C. Skourlas, A. Le Gall, S. Katsikas and K. Karapoulios, "Genetic Algorithms Applications to Software Testing". In Proceedings of Fifth International Conference. Software Engineering and Its Applications, Toulouse, France. pp. 625 - 636, December 1992.
PAGE 2 REF _Ref43974102 \h Introduction
PAGE 3
REF _Ref43964036 \h Introduction
REF _Ref38793633 \h Etat de lart
PAGE 28
PAGE 13
REF _Ref38793653 \h Le test de logiciel
PAGE 15
REF _Ref38794195 \h Éléments de la notation UML pour le test de logiciels OO
PAGE 21
REF _Ref38794679 \h Méthodologie pour une conception testable
PAGE 25
REF _Ref38794641 \h La génération automatique de cas de test
PAGE 27
REF _Ref38794604 \h Conception par contrat et test
PAGE 29
REF _Ref38794734 \h Testabilité et mesures pour les logiciels OO
PAGE 30
REF _Ref38794572 \h Conclusion
REF _Ref43973206 \h Génération automatique de cas de test pour un composant
PAGE 68
PAGE 41
REF _Ref43736049 \h Adaptation de lanalyse de mutation pour la qualification de composants
PAGE 49
REF _Ref43736081 \h Algorithme génétique pour loptimisation de cas de test
PAGE 53
: REF _Ref43736107 \h Etudes de cas pour un algorithme génétique
PAGE 63
REF _Ref43736136 \h Une approche adaptative : un « algorithme bactériologique »
PAGE 67
REF _Ref45004351 \h Paramétrage des modèles
PAGE 65
REF _Ref43736165 \h Paramétrage des modèles
Paramétrage du modèle bactériologique
Lunique paramètre quil faut fixer pour lalgorithme bactériologique est la taille des bactéries. Dans le cas du test unitaire, cette taille est définie par le nombre dappels de méthode inclus dans un cas de test (cf. section 3.4.2). Pour le test système, la taille dune bactérie dépend du format des données dentrée pour le programme sous test. Dans le cas du parseur C#, la taille est donnée par le nombre de nuds de larbre syntaxique contenu dans un cas de test. Cette modélisation de la taille dune bactérie pourrait sappliquer à tout logiciel qui transforme des données dun format à un autre, notamment des logiciels fondés sur XML. La Figure 31, donne la courbe en trois dimensions et sa projection plane (des niveaux de gris exprimant les variations du score de mutation) pour la relation entre la taille de la bactérie, le score de mutation et le nombre de générations. Notons que cette courbe garde la même allure, même en répétant les expérimentations (doù la ligne médiane en noir sur la figure). Les conclusions sont les suivantes :
si la bactérie est trop petite ( val_seuil alors sauvegarde B
Le type de lalgorithme dépend de la valeur de seuil:
si val_seuil = 100 alors pur génétique
si val_seuil = 0 alors pur bactériologique
si 0 < val_seuil < 100 alors approche mixte
Deux critères sont pris en compte pour déterminer la meilleure valeur pour le seuil de mémorisation:
minimisation du nombre de bactéries sauvegardées à la fin du processus, pour minimiser le temps dexécution et la difficulté dinterprétation des cas de test. La Figure 32 montre limpact de la valeur de seuil sur le nombre de bactéries mémorisées.
minimisation du nombre de générations pour atteindre le score de mutation optimal. La Figure 33 présente limpact de la valeur de seuil sur la vitesse de convergence.
Figure 34 Évolution du score de mutation avec une approche mixte
La Figure 32 montre que le nombre de bactéries sauvegardées décroît régulièrement avec laugmentation de la valeur du seuil de mémorisation, la Figure 33 indique que la vitesse de convergence augmente lorsque le seuil dépasse 30%. Un compromis peut être trouvé pour minimiser ces deux valeurs lorsque le seuil se situe entre 20 et 30%. Dans ce cas, la vitesse de convergence est satisfaisante (autour de 30 générations pour atteindre le score optimal) et lensemble final de bactéries contient 7 bactéries. La Figure 34 montre les résultats obtenus pour une valeur de seuil de 25%.
En conclusion, lapproche mixte permet de réduire la taille de lensemble final de cas de test de 10 à 7. Par contre, le score de mutation de 95% nest atteint quaprès 25 générations au lieu de 20 dans le cas de lalgorithme bactériologique (cf. Figure 29). Le gain de cette approche mixte nous semble donc peu significatif par rapport à leffort de paramétrage quil nécessite. En effet, de nombreuses expérimentations sont nécessaires pour trouver un bon compromis pour la valeur de seuil de mémorisation. A linverse, lapproche bactériologique semble plus facile à réutiliser et généraliser.
REF _Ref42515963 \h Robustesse et diagnosabilité : impact de la conception par contrat sur un assemblage de composants
PAGE 96
PAGE 71
REF _Ref38805404 \h Conception par contrat et élaboration dune mesure
PAGE 89
REF _Ref38805184 \h Mesure de la robustesse
PAGE 99
REF _Ref38801738 \h Une mesure de la diagnosabilité
REF _Ref43734968 \h Anti-patterns de testabilité dans un assemblage de composants
PAGE 134
PAGE 103
REF _Ref43735009 \h Présentation de la problématique
PAGE 107
REF _Ref43735177 \h Testabilité dune architecture OO: définitions et méthodologie
PAGE 115
REF _Ref43735277 \h Critère de test et anti-patterns pour les architectures OO
PAGE 121
REF _Ref43735349 \h Modélisation des anti-patterns
PAGE 123
REF _Ref43735402 \h Amélioration de la testabilité du modèle
PAGE 129
REF _Ref43735442 \h Exemples d'application
PAGE 133
REF _Ref43735513 \h Design Patterns pour la testabilité du modèle
PAGE 137
REF _Ref43735553 \h Analyse de testabilité des design patterns
REF _Ref41291829 \r \h Erreur ! Source du renvoi introuvable.
PAGE 142
REF _Ref36868682 \h Conclusions et perspectives
PAGE 142
PAGE 143
REF _Ref43972767 \h Contributions majeures
REF _Ref43966430 \r \h Annexe B
PAGE 148
REF _Ref43964619 \r \h Annexe A
PAGE 148
PAGE 149
REF _Ref43964619 \r \h Annexe A
REF _Ref43966430 \r \h Annexe B
PAGE 152
PAGE 153
REF _Ref43966430 \r \h Annexe B
REF _Ref43966600 \h Glossaire
PAGE 156
PAGE 157
REF _Ref43966600 \h Glossaire
REF _Ref43966702 \h Bibliographie
PAGE 168
PAGE 167
REF _Ref43966702 \h Bibliographie