Utilisation de méthodes incomplètes pour la génération automatique de tests abstraits. Ghislain Charrier August 2007
2
Remerciements Tout d abord, je remercie M. Jacques Julliand, Directeur du Laboratoire d Informatique de l Université de Franche-Comté (LIFC), pour m avoir accueilli au sein de son laboratoire pour ce stage. Je tiens aussi à remercier M. Fabrice Bouquet, Maître de Conférences HDR au LIFC, pour m avoir encadré durant ce stage. Il a été présent pour me guider, me motiver et a su m aider à résoudre mes problèmes en me guidant sur la bonne piste pour trouver la solution plutôt qu en me la donnant. Je souhaite aussi lui témoigner tout mon respect pour avoir su garder son enthousiasme malgré la défaite cuisante de Marseille face à Sochaux en Coupe de France! Je remercie également M. Pascal Chatonnay, Maître de Conférences au LIFC, qui a accepté de faire partie du jury d évaluation de ce stage. Je souhaite aussi remercier tout le personnel du LIFC pour son accueil chaleureux. L ambiance de travail présente au sein de ce laboratoire est très appréciable. Je remercie particulièrement certains doctorants qui se reconnaîtront pour l accueil qu ils réservent aux étudiants de Master 2. Enfin, je remercie mes collègues de Master 2 pour m avoir permis de travailler dans la bonne humeur, et pour m avoir aidé à relire mon mémoire.
4
Table des matières 1 Introduction 9 2 Prérequis 11 2.1 Java Modeling Language (JML)................................. 11 2.2 JML-Testing-Tools (JML-TT).................................. 12 2.3 Synthèse.............................................. 14 3 Les méta-heuristiques 17 3.1 Présentation générale....................................... 17 3.2 Les méta-heuristiques à population............................... 18 3.2.1 Les algorithmes génétiques............................... 18 3.2.2 Les algorithmes bactériologiques............................ 20 3.2.3 L optimisation par colonies de fourmis......................... 20 3.3 Les méta-heuristiques à parcours................................ 22 3.3.1 Le recuit simulé..................................... 22 3.3.2 La recherche tabou.................................... 23 3.4 Autres méta-heuristiques..................................... 24 3.5 Synthèse.............................................. 25 4 Travail réalisé 27 4.1 L exemple d étude préalable................................... 27 4.2 Familiarisation et étude d adaptation des méta-heuristiques................. 28 4.2.1 L algorithme génétique.................................. 28 4.2.2 L algorithme bactériologique.............................. 29 4.2.3 La colonie de fourmis.................................. 29 4.2.4 Le recuit simulé..................................... 31 4.2.5 La recherche tabou.................................... 32 4.3 Contributions à JML-TT.................................... 33 4.3.1 La recherche aléatoire.................................. 33 4.3.2 Méta-heuristiques.................................... 34 4.4 Expérimentations......................................... 34 4.5 Synthèse.............................................. 34 5 Conclusion 37 A Porte monnaie électronique 41 B Demoney 43
6 TABLE DES MATIÈRES
Table des figures 2.1 Exemple JML........................................... 13 2.2 Architecture de JML-TT..................................... 14 2.3 Interface de JML-TT....................................... 15 3.1 Algorithme génétique....................................... 19 3.2 L opérateur de croisement.................................... 19 3.3 L opérateur de mutation..................................... 19 3.4 Algorithme bactériologique................................... 20 3.5 Recherche du plus court chemin vers la nourriture...................... 21 3.6 Algorithme d optimisation par colonies de fourmis...................... 22 3.7 Algorithme du recuit simulé................................... 23 3.8 Algorithme de la recherche tabou................................ 24 3.9 Comparaison des méta-heuristiques............................... 25 4.1 Représentation du problème................................... 27
8 TABLE DES FIGURES
Chapitre 1 Introduction L informatique prend une place de plus en plus importante dans notre vie de tous les jours. Ses applications sont de plus en plus vastes. Celles-ci vont du programme gérant le temps de cuisson d un grille-pain à la gestion de sondes spatiales. En considérant les deux exemples précédents, on s imagine bien qu il existe différentes catégories de programmes informatiques. Ainsi, on distingue les systèmes dits critiques et les systèmes dits non critiques. La technique utilisée pour catégoriser les systèmes critiques et non critiques se base sur les répercussions en cas de défaillance. Plus les conséquences sont graves (mise en danger d une personne, risques financiers), plus le logiciel sera critique. Si le grille-pain laisse griller une tranche trop longtemps une fois sur mille, les conséquences sont minimes. Par contre, si un module spatial commet une erreur une fois sur mille, les risques sont bien plus élevés. Il est possible que le module retombe sur terre et fasse des blessés, voire des morts, surtout si l impact est dans une ville. De plus, la perte financière est énorme pour l entreprise qui perd l engin. Il est donc nécessaire que la confiance dans un logiciel de ce type soit maximale. Durant la phase de développement de logiciels, un cahier des charges est produit. À partir de ce document non formel, il est possible de tirer des informations sur ce que le programme devra être capable de réaliser. De même, le document contient souvent des données sur ce que l application ne doit pas pouvoir faire. Afin de pouvoir augmenter la confiance dans le logiciel produit, le logiciel est souvent représenté par un modèle formel (ou spécification), généralement abstrait. La spécification étant réalisée par des experts, de façon formelle, il est possible d effectuer des calculs mathématiques sur celle-ci. On peut notamment vérifier différentes propriétés. Une propriété est un ensemble d états du système où il est obligatoire ou impossible d aller. Pour vérifier cela, il existe différentes techniques, telles que le Model Checking [EMC99] ou la preuve [Abr96]. Ces méthodes font de la vérification. La vérification décrite précédemment ne fait que vérifier le modèle. Elle ne fait donc qu augmenter la confiance dans la spécification. Mais l important est de s assurer que c est le logiciel lui-même qui est sûr. Il est donc possible d utiliser une autre technique pour voir si le logiciel est bien conforme au modèle. La méthode complémentaire à la vérification est la validation. Celle-ci permet d augmenter la confiance dans le logiciel. La validation correspond aux tests du logiciel. Il existe deux catégories de tests : les tests dits boîte noire et ceux dits boîte blanche. Les tests boîte noire, ou fonctionnels, vérifient qu une opération donnée retourne le résultat escompté. Il n est donc pas nécessaire de savoir comment le calcul s effectue. Les tests boîte blanche, ou de robustesse, vérifient que les opérations n effectuent pas de comportements inattendus dans le cas où les arguments de l opération ne sont pas conformes à ce que l on attend. Il est possible de générer des tests à la main, mais aussi automatiquement. Les réaliser automatiquement présente plusieurs avantages. Tout d abord, cela permet de combler certains oublis qu un humain aurait pu faire, mais aussi de ne pas commettre d erreurs. Ensuite, il est en général bien plus rapide
10 Chapitre 1. Introduction de générer 1000 tests automatiquement que 10 tests manuellement. Il est tout de même nécessaire de continuer à en effectuer à la main, les tests automatiques ne pouvant généralement pas être exhaustifs et ne permettant pas d atteindre toutes les cibles de tests désirées. Pour produire ces tests automatiquement, il existe une technique appelée le Model-Based Testing décrite en 2006 par Mark Utting and Bruno Legeard dans [UL06]. Les tests créés sont générés à partir du modèle. Il est important que le modèle soit conforme si l on veut que les tests soient corrects. La phase de vérification du modèle décrite précédemment est donc primordiale. Le Laboratoire d informatique de Franche-Comté a développé un animateur symbolique de modèles JML nommé JML-TT. L animation est notamment utilisée pour réaliser des tests fonctionnels abstraits. Ces tests sont réalisés à la main ou automatiquement. La génération automatique est actuellement un point faible de l approche utilisée dans JML-TT. Pour réaliser les tests fonctionnels, il faut parcourir un maximum de comportements du modèle. Un comportement représente un chemin d exécution d une méthode. Afin de résoudre ce problème, l utilisation de méthodes incomplètes a été envisagée. Les méthodes incomplètes sont souvent utilisées pour résoudre des problèmes dit difficiles. Sur cette classe de problèmes, les méthodes classiques n arrivent pas à trouver une solution en temps raisonnable. Les techniques envisagées sont la génération aléatoire de tests et l utilisation de méta-heuristiques pour essayer de combler cette lacune de JML-TT. Le document commence tout d abord par un rappel sur ce que sont JML et JML-TT dans le chapitre 2. Un état de l art sur les méta-heuristiques est ensuite présenté dans le chapitre 3, puis les contributions effectuées sur la génération de tests pour JML-TT sont données dans le chapitre 4. Enfin, le bilan et les perspectives concluent le document dans le chapitre 5.
Chapitre 2 Prérequis Afin de bien comprendre la problématique posée dans ce mémoire, ce chapitre présente le langage de spécification JML et l animateur symbolique JML-TT. 2.1 Java Modeling Language (JML) JML 1 [LBR99] est un langage de spécification développé pour le langage Java. Il est basé sur le paradygme de la programmation par contrat. Brièvement, la programmation par contrat permet d assurer le résultat si les préconditions sont respectées. Si ce n est pas le cas, rien n est garanti. On ne sait pas si le résultat obtenu a un sens. JML utilise la logique de Hoare, les pré- et postconditions et les invariants. Une précondition est une formule logique qui permet de dire quelle valeur un attribut ou un paramètre peut prendre à l appel d une méthode. Une précondition possible est que la vitesse de notre voiture doit être égale à 0 km/h si l on veut monter à l intérieur (pre : vitesse = 0). On peut bien sûr donner plusieurs préconditions. On peut ajouter par exemple que l on doit être à l extérieur de la voiture avant de monter dedans (pre : vitesse = 0 & dansv oiture = faux). Une postcondition représente l état dans lequel le système doit se trouver après l exécution de l opération. Par exemple, après être monté dans la voiture (si la vitesse était bien à 0 km/h) l état doit être que l on est dans la voiture (post : dansv oiture = vrai). Il est aussi bien sûr possible d avoir plusieurs postconditions. Si la précondition est respectée, la programmation par contrats assure alors que la postcondition sera atteinte. Dans le cas contraire, rien n est garanti. On ne peut pas connaître le résultat qui sera obtenu. L exemple illustre bien le paradygme de programmation par contrat. En effet, si la voiture n est pas à 0 km/h lorsque l on essaie de monter dedans, il se peut que l on se retrouve à l intérieur si elle allait très doucement, mais il est aussi possible que la voiture aille vite et que l on ne puisse pas monter dedans. L invariant, quant à lui, représente les états dans lesquels le système est obligé de rester. Pour continuer l exemple précédent, un invariant possible est que la vitesse ne doit pas être négative (inv : vitesse >= 0). Le système est donc obligé de rester dans des états où la vitesse est nulle ou encore dans des états où la vitesse est positive. Enfin, la logique de Hoare permet de vérifier des propriétés sur le système. Cette logique est utilisée dans la preuve de programmes, notamment dans la méthode B [Abr96]. Maintenant que l on a compris la technique sur laquelle se base JML, intéressons-nous à sa syntaxe. Les mots clés les plus utilisés dans JML sont les suivants : 1. http://www.jmlspecs.org
12 Chapitre 2. Prérequis requires : définit une précondition ensures : définit une postcondition invariant : définit un invariant assignable : définit les attributs qui vont être modifiés signals : signale l envoi d une exception also : sépare les différents comportements d une méthode Les assertions JML lignes de code représentant les pré-post conditions... sont données en commentaires dans le code source java. Elles se distinguent des commentaires normaux et de la Javadoc en étant précédées du signe. Afin de comprendre le fonctionnement principal de JML, nous allons étudier l exemple 2 d un compte bancaire où le compte peut être verrouillé et où un plafond est présent. Le code de cet exemple est donné dans la figure 2.1. Tout d abord, il y a un invariant garantissant que le solde du compte ne peut être négatif et qu il ne peut excéder un plafond (ligne 1). Ensuite, le constructeur de la classe est décrit. Il n y a pas de précondition, et le résultat assuré est que le solde sera égal à 0 (lignes 2-3). Dans un troisième temps, la méthode crediter est spécifiée (lignes 4-6). Elle prend un montant en paramètre et assure que le nouveau solde sera égal à l ancien solde plus le montant. Il n est pas nécessaire de vérifier dans la méthode si le nouveau solde dépasse le plafond car ceci est spécifié dans l invariant. Si l exécution de la méthode viole l invariant, l exécution ne peut s effectuer. La méthode debiter est similaire à la méthode crediter. Au lieu d ajouter le montant, on le retire au solde. La méthode lockcompte permet de vérouiller le compte. Il n y a pas de précondition, et une seule variable est modifiée (ligne 10). Enfin la méthode getsolde permet de connaitre le solde courant. Si le compte est vérrouillé, une exception est renvoyée (ligne 11). JML est le langage utilisé pour créer les modèles à partir desquels nous ferons la génération de tests. Afin de bien comprendre comment ces tests seront générés, nous allons maintenant étudier JML-TT. 2.2 JML-Testing-Tools (JML-TT) JML-TT 3 [BDL06] est un animateur symbolique de modèles décrits en JML et un générateur de tests pour le langage Java. Il a été développé au sein du LIFC et est basé sur la technologie BZ-TT (B and Z Testing Tools) [ABC + 02]. L architecture principale de JML-TT est présentée dans la figure 2.2. Le logiciel prend en entrée un fichier de spécification JML. La conformité du fichier est ensuite vérifiée à l aide d un analyseur syntaxique et d un vérificateur de type. Par la suite, il est transformé en fichier BZPE, format intermédiaire utilisé en entrée du moteur d animation. Cette transformation permet de réutiliser le moteur développé pour BZ-TT en lui ajoutant seulement quelques fonctions spécifiques pour JML. Le format intermédiaire permet aussi de représenter des diagrammes UML ou des Statecharts. Une fois le fichier JML transformé en BZPE, il est possible d animer le modèle. Le moteur d animation est développé en Prolog à l aide de la programmation logique à contraintes. Cette dernière permet de donner des variables avec leurs contraintes sur le domaine de valeur qu elles peuvent prendre. Par exemple, si la variable test est un entier qui doit être compris entre 10 et 100, son domaine sera représentée par test : 2. Source : http://fr.wikipedia.org/wiki/java_modeling_language 3. http://lifc.univ-fcomte.fr/~jmltt
Chapitre 2. Prérequis 13 public class CompteBancaireExemple { public static final int MAX_SOLDE = 1000; private int solde; private boolean islocked = false; 1 // invariant solde >= 0 && solde <= MAX_SOLDE; 2 // assignable solde; 3 // ensures solde == 0; public CompteBancaireExemple(); 4 // requires montant > 0; 5 // ensures solde = \old(solde) + montant; 6 // assignable solde; public void crediter(int montant); 7 // requires montant > 0; 8 // ensures solde = \old(solde) - montant; 9 // assignable solde public void debiter(int montant); 10 // ensures islocked == true; public void lockcompte(); 11 // signals (CBException e) islocked; public /* pure */ int getsolde() throws CBException; } Figure 2.1 Exemple JML 10..100. Cette technique permet de vérifier facilement que, si l on passe un paramètre à une fonction, sa valeur ne soit pas en-dehors de son domaine. Il est également possible de contraindre les variables entre elles. En effet, on peut spécifier que la variable X doit toujours être inférieure à la variable Y, par la contrainte X < Y. Les deux principales actions réalisables par l animateur symbolique sont la création d instance et l exécution de méthodes. La création d un objet permet l utilisation d un constructeur donné par l utilisateur, mais aussi, la construction par défaut, celle correspondant à l appel au constructeur de la classe Object. Quant à elle, l exécution d une méthode nécessite une instance sur laquelle s exécuter, et une liste de paramètres si nécessaire. Il est possible de ne pas donner de valeur explicite aux paramètres, ceux-ci seront alors contraints. Un paramètre contraint est un paramètre auquel aucune valeur n a été donnée, les modifications sur le système qu il produit seront aussi contraintes. Par exemple, si l on exécute une méthode ajoutant une certaine somme à un compte bancaire, le solde sera égal à son ancienne valeur ajoutée de la valeur du paramètre, mais on ne connait pas la valeur réelle. L opération de somme est gardée en mémoire jusqu à la valuation du paramètre. L action de valuation permet donc de donner des valeurs concrètes aux variables n en possédant pas. Si l on reprend l exemple précédent, lorsque le paramètre sera valué, on connaîtra la valeur du nouveau solde. Valuer a posteriori peut violer l invariant. Il est donc possible de vérifier que celui-ci est toujours valide. Si ce n est pas le cas, la valuation est annulée. La valuation dans JML-TT est une valuation aux bornes. Cela signifie que la première valeur possible pour un paramètre est sélectionnée. Si l opération contient une condition, l opération comprend deux comportements, un comportement étant un chemin d exécution. Il est alors possible de demander que la valuation trouve une valeur pour le comportement
14 Chapitre 2. Prérequis Figure 2.2 Architecture de JML-TT que l on choisit. Le générateur de tests de JML-TT utilise un algorithme de type A* avec heuristique de recherche. Le but de JML-TT n étant de ne générer que des tests fonctionnels, le générateur de tests vérifie avec l animateur que les tests générés sont corrects. Les séquences d appels à des fonctions produites respectent donc les contraintes données par le modèle. Les tests générés sont des tests abstraits car il ne s agit pas de code exécutable. Les tests abstraits correspondent à une animation du modèle. 2.3 Synthèse Ce chapitre nous a présenté les prérequis nécessaires à la suite de la lecture de ce document. Tout d abord, Nous avonc vu une présentation succinte de JML en partie 2.1, puis une présentation générale de JML-TT en partie 2.2. Concernant JML, nous avons vu son fonctionnement, son utilité, ainsi que sa syntaxe. Un exemple simple modélisant un compte bancaire a été donné afin de bien cerner les différents points mis en œuvre par JML. Nous avons notamment vu le principe de la programmation par contrats, les préet postconditions, et les invariants. Nous avons ensuite présenté l architecture de JML-TT. Nous avons vu les deux princupaux aspects : l animateur symbolique de modèles JML, et le générateur de tests. Ces deux aspects sont en relation. En effet, le générateur de tests utilise l animateur pour ne générer que des tests fonctionnels. Pour finir, nous avons vu les différentes actions réalisables par l animateur. Ces actions comprennent notamment la création d instance, l exécution de méthode et la valuation de paramètres. La figure 2.3 présente l interface graphique disponible pour animer des modèles JML à la main. Maintement que nous avons tous les éléments en main pour travailler sur la génération de tests, nous alons étudier les différents algorithmes envisagés pour générer des tests abstraits.
Chapitre 2. Prérequis 15 Figure 2.3 Interface de JML-TT
16 Chapitre 2. Prérequis
Chapitre 3 Les méta-heuristiques Sommaire 3.1 Présentation générale................................. 17 3.2 Les méta-heuristiques à population......................... 18 3.2.1 Les algorithmes génétiques.............................. 18 3.2.2 Les algorithmes bactériologiques........................... 20 3.2.3 L optimisation par colonies de fourmis........................ 20 3.3 Les méta-heuristiques à parcours.......................... 22 3.3.1 Le recuit simulé.................................... 22 3.3.2 La recherche tabou................................... 23 3.4 Autres méta-heuristiques............................... 24 3.5 Synthèse......................................... 25 Cette partie fait un état de l art sur les méta-heuristiques. Tout d abord, une présentation globale est donnée, puis les deux grandes catégories sont présentées : les algorithmes appartenant aux métaheuristiques à population (cf 3.2), puis ceux faisant partie des méta-heuristiques à parcours (cf 3.3). 3.1 Présentation générale Les méta-heuristiques [Lag02] sont apparues dans les années 80. Ce sont des méthodes d optimisation, de type stochastique, de problèmes difficiles. De telles méthodes sont incomplètes. En effet, il n est jamais garanti que leur utilisation aboutisse à la découverte d une solution. Il est encore moins sûr qu une solution optimale soit atteinte. Le terme méta-heuristique provient de la concaténation des mots méta ( au-delà en grec) et heuristique ( trouver en grec). L interprétation de ces mots peut signifier que l on effectue des recherches à un très haut niveau. En effet, les méta-heuristiques sont très génériques, et permettent de s adapter à une grande classe de problèmes. Le but des méta-heuristiques est de minimiser (ou de maximiser) une (ou plusieurs) fonction(s) objectif. Par exemple, on peut vouloir trouver le plus court chemin entre un point A et un point B, sachant que des obstacles sont présents (minimisation). On peut aussi vouloir trouver comment effectuer le plus grand nombre de tâches en un temps donné (maximisation). L évolution des méta-heuristiques se fait de manière itérative. Ceci a l avantage de permettre l arrêt de l algorithme quand on le souhaite, et de récupérer la meilleure solution trouvée jusqu à présent. Cela permet par exemple de ne jamais arrêter l algorithme tant qu il n a pas atteint de solution satisfaisante sauf si l utilisateur le demande. Il n est pas obligé d attendre la fin de l exécution pour obtenir un résultat. Cependant, plus l algorithme travaille longtemps, plus il y a de chances que la solution s améliore.
18 Chapitre 3. Les méta-heuristiques Un point important des méta-heuristiques est qu elles font évoluer des solutions en les améliorant à chaque itération. Il faut qu il soit possible d évaluer ces solutions. En effet, il est toujours nécessaire de pouvoir les comparer afin de retenir la meilleure. Une solution est considérée comme meilleure si elle est plus proche du résultat attendu qu une autre. En général, une part d aléatoire est présente afin d empêcher l algorithme de tomber dans un minimum local. La part d aléatoire est différente selon les algorithmes. Il existe des méta-heuristiques à méthode implicite, où la part d aléatoire ne suit pas une loi donnée algorithmes génétiques (3.2.1), par exemple, et des métaheuristiques à méthode explicite, où une distribution de probabilité suit une loi définie à chaque itération les colonies de fourmis (3.2.3), par exemple. La solution initiale est souvent choisie au hasard. Elle évolue ensuite pour s améliorer. Les méta-heuristiques se découpent en deux grandes catégories [HGH99] : les méta-heuristiques à population (3.2), et les méta-heuristiques à parcours (3.3). Les algorithmes présentés dans chacune de ces catégories sont les plus classiques. 3.2 Les méta-heuristiques à population Les méta-heuristiques à population utilisent un ensemble de solutions en parallèle pour avancer. C est en travaillant sur toutes ces solutions en même temps que cette classe de méta-heuristiques avance dans la recherche d une solution. Dans la suite, nous allons voir les algorithmes les plus connus de cette classe. 3.2.1 Les algorithmes génétiques L algorithme génétique est l un des plus connus parmis les méta-heuristiques. Origine Les algorithmes génétiques tirent leur origine de la nature elle-même. En effet, la génétique a montré que chaque être vivant est constitué d un ADN qui lui est propre. Cet ADN constitue le patrimoine génétique de l individu et il est lui-même constitué de gènes. Chaque gène représente une caractéristique. Lorsque deux individus se reproduisent, leur gènes se mélangent pour former un nouvel individu. Ce dernier obtient ainsi une partie du génome de chacun de ses parents. Il a été remarqué que les individus étant les mieux adaptés à leur environnement de vie sont ceux qui ont le plus de chance de survivre. Si deux individus forts se reproduisent ensemble, il est probable que leurs forces ne soient pas sur les mêmes points, et il est possible que l individu produit obtienne toutes les forces de ses parents. De temps en temps, une mutation intervient dans le génome d un individu. Cette mutation transforme un gène en un autre. Ceci peut créer un gène plus résistant (par exemple, des pommes pouvant résister aux vers de terre) ou un gène plus faible (par exemple, une tumeur). Si l on étudie une population d individus assez longtemps, on remarque que les mieux adaptés sont les mieux enclins à survivre et que des mutations occurent de temps à autre. C est sur ce principe que les algorithmes génétiques ont été développés. Algorithme général Un algorithme génétique assez générique peut se dérouler comme illustré dans la figure 3.1. Dans les algorithmes génétiques présentés par Lawrence Davis en 1987 [Dav87], une population consiste en un ensemble d individus. Un individu représente une solution potentielle au problème posé. Chaque individu est composé de gènes. Pour la représentation d un individu, il existe différentes techniques :
Chapitre 3. Les méta-heuristiques 19 initialisation du temps création de la population initiale tant que (il n y a pas de solution satisfesante) et (le temps limite n est pas atteint) faire : incrémentation du temps sélection des parents calcul des gènes des nouveaux-nés par recombinaison des parents mutations aléatoires à la population évaluation de l adaptation de chaque individu sélection des survivants Figure 3.1 Algorithme génétique La représentation binaire : chaque gène est représenté par 0 ou 1 La représentation par une chaîne de caractères : la représentation est plus complexe et permet de s adapter à un plus grand nombre de problèmes La représentation en arbre : cette représentation est utilisée pour les problèmes où les solutions peuvent être infinies Suivant la représentation choisie, le temps de calcul peut changer. Plus la représentation est simple, plus les calculs sont rapides. Afin de faire évoluer la population, il existe plusieurs opérateurs. Les principaux sont les opérateurs de croisement, de mutation, d évaluation et de sélection. L opérateur de croisement sert à croiser deux individus au sens biologique. Deux individus croisés créent des descendants mélangeant les gènes de leurs parents. Si A et B sont deux individus, avec un génome de longueur n, leurs descendants C et D peuvent être calculés comme illustré dans la figure 3.2. A = G 11,..., G 1i, G 1i+1,..., G 1n C = G 11,..., G 1i, G 2i+1,..., G 2n B = G 21,..., G 2i, G 2i+1,..., G 2n D = G 11,..., G 1i, G 2i+1,..., G 2n Figure 3.2 L opérateur de croisement L opérateur de mutation modifie un gène d un individu. Il est possible à chaque itération de modifier un nombre quelconque d individus. Le gène modifié est choisi aléatoirement et la nouvelle valeur est choisie également aléatoirement dans le domaine des valeurs que le gène peut prendre. Une mutation est illustrée dans la figure 3.3. A = G 11,..., G 1i,..., G 1n A mut = G 11,..., G 1iMut,..., G 1n Figure 3.3 L opérateur de mutation L opérateur d évaluation est un aspect important des algorithmes génétiques. Un individu est évalué en fonction d une cible à atteindre. Plus l évaluation est bonne, plus il a de chance de survivre. On retrouve bien ici l idée tirée de la nature où les individus les mieux adaptés survivent plus facilement. Le dernier opérateur est celui de sélection. Il sert à choisir les individus qui vont survivre à l itération courante en fonction de leur évaluation. Cette sélection peut s effectuer de différentes manières. Il est possible de ne garder que les X meilleurs individus, mais dans le cas général, c est le principe de la roulette
20 Chapitre 3. Les méta-heuristiques biaisée qui est appliqué : la probabilité de survie d un individu est proportionelle à son évaluation. On peut noter qu en utilisant la méthode de la roulette biaisée, il est possible de perdre de bons individus et de garder des mauvais. Pour minimiser ce risque, il est possible de garder automatiquement en mémoire les meilleurs individus, tout en continuant d utiliser la roulette pour la sélection des individus. 3.2.2 Les algorithmes bactériologiques L algorithme bactériologique est basé sur les algorithmes génétiques. Ils sont donc assez similaires. Origine Les algorithmes bactériologiques sont apparus récemment en tant que méta-heuristique. Ils ont été développés par l équipe Triskell 1 à Rennes, notament grâce à la thèse de Benoit Baudry [Bau05]. Lors du développement d un générateur de cas de tests utilisant la technique du mutation-based testing [BFJT05a] en utilisant les algorithmes génétiques, l équipe s est aperçu que l utilisation d algorithmes génétiques n était pas bien adaptée. En effet, les nouveaux cas de tests n étaient créés que par l opérateur de mutation, le croisement ne faisant que réécrire des cas déjà existants. L équipe à donc eu l idée d utiliser la reproduction des bactéries, qui se multiplient en se clonant et des mutations s opèrent. Les bactéries ayant le meilleur patrimoine génétique sont celles qui survivent le mieux dans leur environnement. Algorithme général Un algorithme bactériologique se déroule de manière très similaire à un algorithme génétique. Son fonctionnement est illustré dans la figure 3.4. initialisation du temps création de la population initiale tant que (il n y a pas de solution satisfaisante) et (le temps limite n est pas atteint) faire : incrémentation du temps mutations aléatoires à la population (Toutes les bactéries mutent) évaluation de l adaptation de chaque bactérie sauvegarde des meilleures bactéries Figure 3.4 Algorithme bactériologique L algorithme bactériologique [BFJT05b] est très similaire à un algorithme génétique. Les différences se situent au niveau du croisement des individus et au niveau de la sélection des individus. En effet, dans cet algorithme, l opérateur de croisement a disparu, et pour ne pas perdre les bactéries les mieux adaptées, les meilleures sont sauvegardées lors de la sélection. A chaque itération, il est possible d en sauvegarder un certain nombre, mais aussi de sauvegarder les X meilleures. Les premières itérations entrainent alors la sauvegarde de toutes les bactéries. Les opérateurs de mutation et d évaluation de cet algorithme sont identiques à ceux des algorithmes génétiques. Pour leur description se référer à la partie 3.2.1. 3.2.3 L optimisation par colonies de fourmis Après avoir présenté des algorithmes basés sur la reproduction biologique, étudions l algorithme des colonies de fourmis qui utilise l intelligence collective d un groupe de fourmis. 1. http://www.irisa.fr/triskell/home_html
Chapitre 3. Les méta-heuristiques 21 Origine L algorithme d optimisation par colonies de fourmis tire son origine de l observation de colonies de fourmis. Dans la nature, les fourmis ne disposent pas d intelligences individuelles évoluées, mais une colonie arrive pourtant à résoudre des problèmes complexes. Un des problèmes auquel sont confrontées les fourmis est la recherche de nourriture. Pour la trouver, les fourmis partent au hasard, et lorsqu elles ont trouvé un point de nourriture, elles reviennent vers la colonie en déposant des phéromones sur le sol. Les phéromones s évaporent au cours du temps. Imaginons deux fourmis qui trouvent le même point de nourriture en ayant deux itinéraires différents et de longueurs différentes. Chaque fourmi repart ensuite vers la colonie en déposant une quantité de phéromones inversement proportionnelle à la longueur du trajet. Plus le chemin est long, plus la quantité de phéromones déposée sera faible. Les phéromones déposées par la fourmi ayant pris le chemin le plus court sont plus concentrées, et s évaporent donc plus lentement. Le choix des autres fourmis pour choisir un chemin vers la source de nourriture se fait ensuite par rapport à la quantité de phéromones sur le chemin. Plus la concentration est élevée, plus la fourmi aura de chances de prendre ce chemin. Afin de ne pas rester bloquées sur un seul chemin et sur un seul point de nourriture, certaines fourmis continuent de prendre des chemins différents, parfois en commençant par suivre une source de phéromones puis en changeant de chemin. La figure 2 3.5 présente la recherche du plus court chemin d une colonie de fourmis N à un point de nourriture F. Il y a 4 chemins possibles. La première fourmi trouve la source de nourriture (F), via un chemin quelconque (a), puis revient au nid (N) en laissant derrière elle une piste de phéromone (b). Ensuite, les fourmis empruntent indifféremment les quatre chemins possibles, mais le renforcement de la piste rend plus attractif le chemin le plus court. Les phéromones du chemin le plus court sont renforcées, alors que les phéromones des chemins les plus longs s évaporent. Figure 3.5 Recherche du plus court chemin vers la nourriture 2. Source : http://fr.wikipedia.org/wiki/algorithme_de_colonies_de_fourmis
22 Chapitre 3. Les méta-heuristiques Algorithme général L algorithme d optimisation par colonies de fourmis se déroule comme illustré dans la figure 3.6. initialisation du temps création de la population de fourmis tant que (il n y a pas de solution satisfaisante) et (le temps limite n est pas atteint) faire : incrémentation du temps déplacement des fourmis évaporation des phéromones autres actions (si nécessaire) Figure 3.6 Algorithme d optimisation par colonies de fourmis L algorithme d optimisation par colonies de fourmis [DS03] est particulièrement adapté pour travailler sur des graphes. En effet, simuler le déplacement des fourmis peut se faire d un nœud à un autre. Les noeuds représentent les différentes positions possibles d une fourmi. En ce qui concerne la déposition de phéromones, il est possible de les déposer sur les arrêtes pour représenter le chemin parcouru. Il est à noter que l utilisation d un graphe n est pas nécessaire. L algorithme crée tout d abord une popultaion initiale de fourmis. En général, cette population est créée sur un nœud choisi (un nœud racine si il y en a un), mais il est possible de mettre toutes les fourmis sur des nœuds différents. Dans un second temps, les fourmis se déplacent. Contrairement à la nature, les phéromones laissées par les fourmis sont souvent déposées au premier passage de celle-ci, et non au retour. Comme dans la nature, les fourmis choisissent le chemin à prendre en fonction des phéromones présentes sur les chemins disponibles au nœud courant. Pour certains problèmes, il est plus avantageux d utiliser des phéromones répulsives. Une fois le déplacement terminé, l évaporation des phéromones est simulée. Il est possible d utiliser différentes techniques pour le taux d évaporation. Par exemple, on peut donner comme formule que le taux de phéromones P h au temps t est égal à : P h(t) = P h(t 1) 0.9. Il est bien sûr possible de définir d autres formules pour l évaporation des phéromones. Cet algorithme peut également être couplé à une recherche locale pour améliorer son efficacité. En effet, en cas de phéromones identiques sur deux chemins, la fourmi devra choisir totalement arbitrairement. Soit E un ensenble de chemins ayant la même quantité de phéromones, la probabilité P r d aller 1 sur chacun de ces chemins est de : P r = card(e). Enfin, des actions dite fantômes sont effectuées. Ces actions sont très dépendantes du problème à traiter, elles ne seront donc pas détaillées ici. 3.3 Les méta-heuristiques à parcours Les méta-heuristiques à parcours ne travaillent qu avec une seule solution. Cette dernière est modifiée à chaque itération de l algorithme. L espace des solutions est visité par la solution, et celle-ci est modifiée pour être améliorée au cours de ce parcours. Les méta-heuristiques à parcours les plus classiques sont présentées ci-après. 3.3.1 Le recuit simulé Tout comme les méta-heuristiques à population présentées en partie 3.2, le recuit simulé vient de l observation d un phénomène naturel. En revanche, l observation ne vient pas du monde du vivant, mais du monde de la physique, et plus précisément de la métallurgie.
Chapitre 3. Les méta-heuristiques 23 Origine Pour qu un métal ait une qualité optimale, il faut que son état d énergie soit minimal. Pour améliorer la qualité d un métal, on le chauffe, puis on le refroidit par paliers en attendant à chaque fois que l état d énergie soit stabilisé. Au niveau de l atome, cela signifie qu à une température chaude, les atomes bougent beaucoup, et en se refroidissant, ils trouvent leur place optimale. Plus la température descend, moins les atomes bougent et le métal devient de plus en plus résistant. Algorithme général Le recuit simulé se déroule comme illustré en figure 3.7. initialisation du temps tant que (il n y a pas de solution satisfaisante) et (le temps limite n est pas atteint) faire : initialisation de la température à T max tant que (la température est supérieure à 0) et (le temps limite n est pas atteint) faire : incrémentation du temps modification de la solution courante si la nouvelle solution est meilleure que l ancienne alors la sauvegarder sinon, si le système est stable depuis un nombre défini d itérations alors diminuer la température Figure 3.7 Algorithme du recuit simulé Le recuit simulé à été introduit en 1983 par S. Kirkpatrick et al. [KGV83]. Il a été repris sous différentes formes par Lawrence Davis en 1987 [Dav87]. Étant une méta-heuristique à parcours, il ne travaille que sur une seule solution et la fait évoluer. La notion de température est prise en compte dans l évolution de l algorithme. Plus la température est élevée, plus la nouvelle solution a de chances d être éloignée de la précédente. À température élevée, l espace d état est exploré largement. Plus la température descend, moins l espace est exploré au global, mais il est visité en local. Par exemple, pour la recherche d un plus court chemin (en passant par tous les points) entre deux nœuds dans un graphe, le recuit simulé travaille par permutations des nœuds intermédiaires. La notion de température entre en compte dans le fait que à température très élevée, il est possible d échanger n importe quels points, alors qu à température très basse, les seules permutations ne s effectuent qu entre des points adjacents. 3.3.2 La recherche tabou La seconde méta-heuristique à parcours est la recherche tabou. Origine Contrairement à toutes les autres méta-heuristiques présentées dans ce mémoire, la recherche tabou ne tire pas son origine de l observation de la nature. Elle a été inventée par Fred Glover en 1986 [Glo89]. Algorithme général Le déroulement de cet algorithme est illustré dans la figure 3.8. Soit s une solution, f(s) l évaluation de cette solution. s est la meilleure solution courante et f = f(s ). Dans la recherche tabou décrite par Manuel Laguna [Lag94], la première solution est tirée au hasard, ou trouvée à l aide d une heuristique gloutonne.
24 Chapitre 3. Les méta-heuristiques initialisation du temps on choisit s0 la solution initiale s* = s0 et f* = f(s0) tant que (il n y a pas de solution satisfaisante) et (le temps limite n est pas atteint) faire : incrémentation du temps choisir s dans le voisinage de s* qui ne soit pas tabou si f(s) < f* alors s* = s et f* = f(s) mise à jour des tabous Figure 3.8 Algorithme de la recherche tabou Cet algorithme fait évoluer la solution s à chaque itération même si cette solution est moins bonne que la solution précédente. Cela n est pas gênant car la meilleure solution s est toujours en mémoire. Cette technique permet d éviter les minimums locaux. L évolution de la solution s se fait par rapport à ses voisins. Pour cela, on calcule N(s) les voisins de s et l on prend le meilleur, même si il est moins bon que s. Ceci peut causer des cycles infinis : s s s s... C est pour éviter ce problème que la liste tabou à été mise en place. Elle sauvegarde les derniers mouvements effectués et interdit de faire un mouvement présent dans la liste. Afin de ne pas avoir de dépassement de mémoire, la taille de cette liste est configurable. Quand la liste est pleine, si l on ajoute un nouvel élément, le plus vieux est supprimé de la liste. La liste est donc une file FIFO. Il n y a que peu de chances de faire un cycle car le temps que la file commence à se vider, le parcours se sera éloigné des mouvements qui ont été sauvegardés dans la liste. Cependant, il n est pas possible de garantir qu un cycle n aura pas lieu dans le cas général. La liste pose un autre problème. En effet, si tous les voisins d une solution ont été explorés, l algorithme se bloque. Il est alors possible d entrer dans une phase nommée l aspiration qui supprime certains éléments de la liste, et l algorithme peut repartir. Pour améliorer l efficacité de la recherche tabou, des techniques ont été mises au point : la diversification et l intensification. Lors d une phase d intensification, une mémoire à long terme est utilisée. Elle sauvegarde les bonnes propriétés rencontrées lorsque l on a trouvé de bonnes solutions. On cherche à reproduire des types de mouvements qui ont permit de trouver de bonnes solutions précédemment. Cette phase permet d explorer un espace restreint de l espace global. À l inverse, la phase de diversification consiste à diriger la recherche dans le sens opposé. Il s agit d essayer d explorer un maximum d espaces différents. Dans la recherche tabou, on considère qu un mauvais choix stratégique nous en apprend plus qu un bon choix aléatoire. Les phases d intensification et de diversification servent à effectuer des choix stratégiques. 3.4 Autres méta-heuristiques Les méta-heuristiques ne se cantonnent pas seulement à celles décrites précédemment. Il en existe d autres qui sont décrites très brièvement dans cette partie. Dans la classe des méta-heuristiques à population, l optimisation par essaim de particules a été inventée en 1995 par Russel Ebenhart et James Kennedy [EK95]. Cette méta-heuristique est basée sur l observation de groupes d oiseaux migratoires. Les oiseaux connaissent leur position et celle de leurs voisins. Avec ces données, les oiseaux arrivent à résoudre des problèmes complexes. Concernant les méta-heuristiques à parcours, une autre méthode existante est celle de Cross Entropy dont De Boer et al. ont réalisé un tutoriel en 2005 [dbkmr04]. Cette méthode se base sur la génération aléatoire de solutions. La génération aléatoire est paramétrée probabilistiquement. Si l on voit que de
Chapitre 3. Les méta-heuristiques 25 nombreuses bonnes solutions contiennent une certaine donnée, la probabilité que cette donnée soit choisie sera augmentée et inversement. 3.5 Synthèse Comme nous avons pu le voir, les méta-heuristiques appartiennent à différentes catégories. Certaines sont inspirées de la nature, certaines contiennent une mémoire... Les différentes méta-heuristiques que nous avons étudié sont : Les algorithmes génétiques Les algorithmes bactériologiques L optimisation par colonies de fourmis Le recuit simulé La recherche tabou Le tableau 3.9 présente quelques points de comparaisons entre ces différentes méthodes. Génétique Bactériologique Fourmis Recuit Simulé Tabou population oui oui oui non non mémoire possible oui oui non oui inspiré de la nature oui oui oui oui non recherche locale non non possible oui oui Figure 3.9 Comparaison des méta-heuristiques Les méta-heuristques permettent de traiter de nombreux problèmes difficiles. Leur utilisation semble donc être une voie prometteuse pour la génération automatique de tests abstraits.
26 Chapitre 3. Les méta-heuristiques
Chapitre 4 Travail réalisé Sommaire 4.1 L exemple d étude préalable............................. 27 4.2 Familiarisation et étude d adaptation des méta-heuristiques.......... 28 4.2.1 L algorithme génétique................................. 28 4.2.2 L algorithme bactériologique............................. 29 4.2.3 La colonie de fourmis................................. 29 4.2.4 Le recuit simulé.................................... 31 4.2.5 La recherche tabou................................... 32 4.3 Contributions à JML-TT............................... 33 4.3.1 La recherche aléatoire................................. 33 4.3.2 Méta-heuristiques................................... 34 4.4 Expérimentations................................... 34 4.5 Synthèse......................................... 34 Pour adapter les méta-heuristiques à la génération de tests fonctionnels abstraits, il est préférable de bien prendre en main ces techniques. C est pour cela que j ai tout d abord développé certaines de ces méthodes sur un exemple de familiarisation. Dans ce chapitre, l exemple de prise en main est décrit en section 4.1 puis la réalisation de cet exemple sur chaque algorithme ainsi que l étude de l adaptabilité au problème de la génération de tests en partie 4.2. Enfin, ce chapitre se termine sur les contributions apportées à JML-TT. 4.1 L exemple d étude préalable L exemple d étude préalable a pour objectif de découvrir une séquence de nombres. Il a été développé en Java. La séquence à trouver est stockée dans un tableau, et les valeurs contenues dans ce tableau sont comprises entre deux valeurs qui sont spécifiées. À la construction du problème, les valeurs min et max sont demandées, ainsi que la longueur n du problème. Le problème est ensuite construit. Soit X un ensemble de nombres de taille n, X i un élément de cet ensemble avec 0 i < n et min X i < max. Le problème est représenté dans la figure 4.1. X 1 X 2... X n 1 X n Figure 4.1 Représentation du problème Il est bien sûr possible de récupérer directement la séquence de nombres stockée dans le problème,
28 Chapitre 4. Travail réalisé mais nous ferons comme si il est impossible de connaître cette séquence. Nous allons donc utiliser des méta-heuristiques pour rechercher la séquence cachée. 4.2 Familiarisation et étude d adaptation des méta-heuristiques Cette section présente l implémentation des méta-heuristiques pour la recherche d une séquence de nombres cachés, ainsi que l étude réalisée pour voir l adaptabilité de ces méthodes au problème de la génération des tests. 4.2.1 L algorithme génétique Prise en main de l algorithme L algorithme génétique implémenté est un algorithme un peu différent de celui donné en section 3.2.1. En effet, chaque individu à une longévité, ce qui signifie que chaque individu, bon ou mauvais, survit un nombre défini d itérations. Cette technique est aussi valable que l utilisation d une roulette biaisée. La population initiale est générée aléatoirement. L opérateur de sélection des survivants n est donc pas implémenté explicitement, mais la sélection est réalisée à l aide de la longévité des individus. La population utilisée est composée d une vingtaine d individus. Les gênes G i des individus correspondent aux nombres à trouver. Concernant l opération de croisement, elle effectue un croisement entre deux individus choisis totalement arbitrairement, puis elle croise les X meilleurs individus (Le premier avec le second, le troisième avec le quatrième,...). Ce choix n est certainement pas le meilleur, mais il est simple à implémeter et faire simple ne gêne pas à le prise en main de l algorithme. Pour calculer les nouveaux individus issus du croisement, on calcule la moyenne gène à gène des parents. Soient deux parents A et B, leur descendant C est calculé de la manière suivante : G C i = GA i + G B i 2 avec 1 i tailleindividu. La mutation est effectuée simplement. Un nombre défini d individus sont sélectionnés au hasard, puis un des gènes de chaque individu est modifié aléatoirement avec une valeur comprise entre min et max. L opération d évaluation est un point important. Il est donc important qu elle soit bien pensée. La valeur absolue de la différence entre chaque gêne et le nombre correspondant dans le problème est calculée, puis chaque différence est ajoutée pour donner la valeur d évaluation. Le calcul de l évaluation d un individu est donné par : Eval = n 1 i=0 X i G i L algorithme génétique développé de cette manière converge assez vite au départ vers la solution. En effet, la moyenne effectuée lors des croisements fait descendre l évaluation des individus très vite. En revanche, à la fin, il faut attendre sur les mutations si l on ne veut pas que l algorithme stagne. Durant les tests, on voit que même pour des problèmes de très grande taille (taille 1000, min = 0 et max = 1000), cet algorithme reste assez efficace. L algorithme a aussi été implémenté en Prolog, le moteur de JML-TT êtant développé dans ce langage. Les performances de la version Prolog étaient moins bonnes, mais l algorithme n était pas bien optimisé.
Chapitre 4. Travail réalisé 29 Étude de l adaptabilité à la génération de tests Dans la cadre de la génération automatique de tests, le but est d augmenter la couverture des séquences de tests dans JML-TT. Afin de représenter des séquences de tests, la représentation des individus la plus adaptée semble être l utilisation de chaînes de caractères. Chaque gène représenterait un appel de méthode. La séquence d exécution serait représentée par l individu. La population initiale peut être choisie aléatoirement. Les appels de fonctions seraient choisis au hasard et conservés si l exécution de la séquence le permet. L opérateur de croisement pose problème. En effet, si l on choisit de croiser deux individus au gène i, il faut vérifier que le gène i 1 de l autre individu est compatible. Il faut donc réexécuter la séquence d appels pour chaque individu produit. Pour palier ce problème, si les gènes ne sont pas compatibles, on peut remplacer le gène i par un autre choisi aléatoirement. Il faut alors vérifier que le gène i + 1 est lui même compatible avec le gène i. Et ainsi de suite jusqu à la fin de la séquence. Un autre problème posé par l opérateur de croisement est qu il n introduit pas de nouvelle opération. Il est pourtant important que tous les comportements du modèle soient testés. L opérateur de mutation pose aussi problème. Tout comme l opérateur de croisement, c est l enchainement de la séquence qui va poser problème. Si le gène i est muté, il faut vérifier que le gène i + 1 soit compatible. L évaluation des individus peut être définie en prenant en compte le nombre de comportements différents appelés. Cette définition semble être adaptée pour maximiser la couverture des tests. Nous avons vu que l algorithme génétique pose problème sur plusieurs points. Les problèmes posés par les opérateurs de mutation et de croisement seront présents à chaque exécution. Même si l opération suivante est compatible, il faut quand même tout ré-évaluer pour que l état du système soit correct. L évaluation n est pas une chose rapide et simple à réaliser. L algorithme génétique ne semble donc pas adapté à la génération automatique de tests dans JML-TT. 4.2.2 L algorithme bactériologique Prise en main de l algorithme L algorithme bactériologique étant très lié à l algorithme génétique (c.f. 4.2.1), celui-ci n a pas été implémenté. En effet, il comprend moins d opérateurs, et est donc plus simple à implémenter. De plus, la recherche d une séquence de nombre avec cet algorithme ne revient qu à des mutations, ou en d autres termes, à trouver la solution en tirant des nombres au hasard. La possibilité que cet algorithme trouve la solution devient alors infime. Étude de l adaptabilité à la génération de tests L étude de cet algorithme est similaire à celle faite pour l algorithme génétique. Il faut prendre en compte les remarques faites à propos de l opérateur de mutation. Tout comme pour l algorithme génétique, la ré-évaluation des séquences de tests est nécessaire à chaque mutation. Cet algorithme ne semble donc pas adapté pour la génération de tests. 4.2.3 La colonie de fourmis Prise en main de l algorithme L algorithme d optimisation par colonies de fourmis étant particulièrement adapté pour les graphes, il a fallu le modifier pour travailler sur le problème. Les nœuds d un graphe sont représentés par des séquences de nombres représentant des solutions potentielles au problème. Les positions initiales des
30 Chapitre 4. Travail réalisé fourmis sont tirées au hasard. Pour les phéromones, il a fallu décider si l on voulait les mettre sur des séquences, ou sur des passages d une séquence à une autre. Nous avons décidé d utiliser les séquences. En effet, ce que nous cherchons à éviter est de repasser plusieurs fois par la même séquence. Les phéromones sont stockées à l aide d une table de hashage. Les clés de hashage sont représentées par la chaîne de caractères correspondant aux séquences qui ont été visitées. La valeur stockée est le nombre de passages sur chaque séquence. Pour savoir où se trouvent les fourmis, chacune connaît la séquence sur laquelle elle travaille. Pour se déplacer, chaque fourmi a le choix d incrémenter ou de décrémenter n importe quel nombre dans la séquence. Bien sûr, le choix s effectue en fonction des phéromones. Chaque fourmi met à jour la table des phéromones après chaque déplacement afin de modifier les probabilités de mouvements des autres fourmis lorsqu elles se déplacent ensuite. Il aurait été possible de mettre les phéromones à jour après que toutes les fourmis aient bougé, mais cela n aurait pas permis de modifier les chances de prendre telle ou telle séquence entre chaque fourmi. Soit N le nombre total de passages sur les séquences où il est possible d aller, max le nombre total de séquences accessibles depuis la séquence courante, N j le nombre de passages sur le chemin j. N est alors calculé par : N = La probabilité P de choisir la séquence i est de : P (i) = max N j j=1 (N + 1) N i ((N + 1) max) N Cette formule permet de bien modifier les probabilités. En effet, il est bien plus probable de choisir une séquence avec peu ou pas de phéromones qu une avec beaucoup de phéromones. Il est même possible de jouer sur les phéromones des successeurs pour que les probabilités soient encore plus modifiées. Par exemple, on peut dire que le nombre de passages N j sur chaque séquence atteignable est égal à : N j = (N j + i) 2 Ceci permet de vraiment biaiser les probabilités pour que les chemins avec le moins de phéromones aient vraiment beaucoup plus de chances d être choisis. Modifier tous les N j ne modifie pas la formule précédente. En effet, N est calculé à partir des N j et N i est lui même un N j. Malgré les modifications possibles des probabilités, l optimisation par colonies de fourmis reste très lente sur la recherche de séquence de nombres. Dès que le problème devient grand, cet algorithme ne trouve pas de solution dans un temps raisonnable. Afin d améliorer les performances de cet algorithme, il aurait été possible d implémenter une recherche locale afin de privilégier les séquences qui rapprochent de la solution, mais comme il ne s agissait que d un exemple de familiarisation, cela n a pas été jugé utile pour mieux prendre l algorithme en main. La recherche locale aurait permis à l algorithme d être convergeant. En effet, si l on prend toujours le meilleur successeur, on se rapproche de 1 de la solution à trouver. Étude de l adaptabilité à la génération de tests L adaptation de l optimisation par colonies de fourmis à la génération de tests nécessite un changement dans l utilisation des phéromones. Si l on veut maximiser la couverture des différents comportements des méthodes, utiliser des phéromones répulsives semble nécessaire. Les phéromones serviraient à se souvenir des comportements déjà parcourus. La table sauvegardant les phéromones contiendrait donc des noms de méthode avec le numéro du comportement qui est appelé en clé, et en valeur, le nombre de passages sur chaque comportement.
Chapitre 4. Travail réalisé 31 Les chemins que parcourent les fourmis seraient représentés par une succession d appels de méthode avec le comportement choisi (correspondant à la clé dans la table des phéromones). Les fourmis avançant au fur et à mesure, sans modifier ce qu elles ont déjà parcouru, il n est pas nécessaire de ré-évaluer des séquences déjà construites. Pour calculer la probabilité de prendre tel ou tel comportement, il est possible d utiliser la formule donnée pour l exemple de familiarisation. Le problème est qu il faut connaître le nombre d appels qui sont réalisables depuis l état courant du système. Ce nombre ne peut être connu qu en exécutant toutes les possibilités. Ceci pose problème dans les cas où de nombreuses fonctions avec plusieurs comportements sont présents. Il faut alors exécuter tous les comportements, et voir combien sont valides. Il possible que seul un comportement soit valide sur une multitude. On perd alors un temps considérable à tester les autres. Il faut penser à une autre technique pour évaluer les probabilités, mais nous n avons pas trouvé de formule ne prenant pas en compte le nombre de comportements. Pour pouvoir effectuer un choix en fonction de probabilités, il semble nécessaire de connaître le nombre total de possibilités valides. La formule donnée semble donc être correcte, mais pas très adaptée. L optimisation par colonies de fourmis pose problème pour le calcul des probabilités. Il semble donc que cette technique ne soit pas adaptée à la génération de tests. 4.2.4 Le recuit simulé Prise en main de l algorithme Pour le recuit simulé, la solution est représentée par une séquence de nombres. La solution initiale est tirée aléatoirement. La température maximum T max est calculée en fonction de la valeur minimum du problème et de la valeur maximale : T max = max min La température courante descend tous les 10 pas d exécution qui s effectuent sans amélioration de la meilleure solution. Le taux de descente de la température est de 0.9. Lorsque la température est inférieure à 1, elle est réinitialisée à T max. L algorithme travaille toujours sur la meilleure solution, et la modifie. Tout d abord, il choisit aléatoirement un nombre de la séquence à modifier, puis tire un nombre entre 0 et la température courante. Ce nombre est ensuite ajouté ou retranché au nombre choisi dans la séquence. L ajout ou le retranchement est choisi aléatoirement. Si la nouvelle solution est meilleure que l ancienne, elle la remplace en tant que meilleure solution globale et le nombre de pas avant la descente de la température est remis à 10. Dans le cas contraire, le nombre de pas descend, et la température aussi si nécessaire. Les performances de l algorithme sur ce problème sont plutôt bonnes. Il lui arrive souvent de trouver la solution assez vite. Les opérations à réaliser étant très simples, les calculs sont très rapides. En nombre d itérations pour trouver la solution, il est souvent bien moins bon que l algorithme génétique, mais il est généralement presque aussi rapide. Étude de l adaptabilité à la génération de tests Comme pour les autres méta-heuristiques, une solution du recuit simulé peut être représentée par une séquence d appels de comportements. Cette méthode pose un problème au niveau de la couverture des comportements. En effet, les modifications de la solution courante se font par permutations. Il n y a donc pas d introduction de nouveaux comportements. Ceux qui ne sont pas dans la séquence au départ ne seront jamais inclus dedans. De plus, lors de la permutation de deux appels, il est nécessaire de ré-évaluer la séquence à partir de l élément permuté le plus proche du début. Le temps perdu est considérable.
32 Chapitre 4. Travail réalisé De par son fonctionnement, le recuit simulé ne semble pas du tout adapté au problème posé dans ce mémoire. 4.2.5 La recherche tabou Prise en main de l algorithme La recherche tabou est basée sur l utilisation d une liste tabou. Il est donc nécessaire que cette liste soit optimisée pour que les performances soient bonnes. Il y a trois opérations à réaliser sur cette liste : l ajout, la suppression et la recherche de la présence d un élément. Ces trois opérations sont particulièrement adaptées à l utilisation d une table de hashage, cependant une telle structure ne permet pas de sauvegarder l ancienneté des éléments. Une liste chaînée permet de connaître l ancienneté des éléments si ils sont tous ajoutés en fin de liste, mais la recherche de la présence d un élément est coûteuse en temps. Pour ne pas perdre de performances, et pour pouvoir tout faire, il a donc été décidé d utiliser une liste chaînée ainsi qu une table de hashage. Lors de l ajout on recherche dans la table si l élément est présent. Si il ne l est pas, il est ajouté en fin de liste et dans la table. Si la liste est pleine, le premier élément est supprimé, ainsi que son homologue dans la table. La recherche de présence est effectuée seulement dans la table. Cette structure mettant en œuvre une table de hashage et une liste chaînée permet de garder performances et fonctionnalités. La représentation de la solution sur laquelle on travaille est faite par l utilisation d une séquence de nombres. La solution initiale est calculée aléatoirement. La liste contient donc des séquences sous forme de chaîne de caractères. Les clés de la table sont les mêmes chaînes. Il n est pas nécessaire de stocker de valeur, la table ne contient donc que des clés avec des valeurs fictives qui ne sont pas utilisées. Sans ces valeurs, il est impossible d ajouter un élément dans la table. C est pour cela que les valeurs fictives sont utilisées. Le calcul des séquences suivantes dans l exécution est faite en ajoutant ou retirant 1 à chaque nombre de la séquence courante. Chaque solution est évaluée, puis la meilleure est choisie. En cas d égalité, la première solution trouvée est choisie. Aucun autre choix autre que celui-là n a été implémenté car il aurait été inutile dans ce cas précis. Faire de cette façon permet à l algorithme d être convergeant. En effet, à chaque itération il se rapprochera de 1 de la solution à trouver. Le nombre total nécessaire à l algorithme est donc calculable. Soit Y i un nombre de la séquence de la recherche. Le nombre d itérations nécessaires pour le calcul de la solution est de : Nb iter = n 1 i=0 X i Y i On peut noter que cette formule est identique à l évaluation des solutions. En effet, l évaluation de la séquence de la recherche tabou s améliorant de 1 à chaque itération, il est normal qu elle soit égale au nombre total d itérations. La recherche tabou telle qu implémentée ici permet d être certain que l on trouvera une solution, et on peut même calculer le nombre d itérations nécessaires. Cette méthode ne converge pas très vite, mais elle est quasiment tout le temps la plus rapide. Pour les problèmes de très grande taille, il arrive que l algorithme génétique soit plus rapide grâce à sa convergence rapide en début d exécution. Étude de l adaptabilité à la génération de tests Pour modéliser la solution de la recherche tabou, une séquence d appels peut être utilisée. Il faudrait partir d une solution vide puis la combler au fur et à mesure en ajoutant des appels à la séquence.
Chapitre 4. Travail réalisé 33 La liste tabou contiendrait des appels déjà ajoutés, ou des suite d appels. Par exemple, si l on appelle A puis B, au lieu de stocker juste A et B, il faudrait ajouter A B. Cela permet d éviter de se bloquer trop rapidement. Si plus aucun comportement n est accessible, il faut alors passer dans une phase d aspiration pour libérer de nouvelles séquences. La recherche tabou ne pose pas de problèmes vraiment gênants. Cette technique semble donc applicable à la génération automatique de tests pour JML-TT. 4.3 Contributions à JML-TT Tout comme pour les méta-heuristiques, il était nécessaire de prendre en main l outil JML-TT, et de comprendre son fonctionnement interne avant de pouvoir développer un nouveau module. Nous avons donc décidé de tout d abord faire un générateur aléatoire paramétré de tests. 4.3.1 La recherche aléatoire Afin de prendre l outil en main, la génération aléatoire de tests semble être une bonne approche. En effet, il n y a pas besoin de trop réfléchir sur l algorithme, ce qui permet de se concentrer sur l outil. Pour animer aléatoirement, l algorithme est très simple. Une méthode est choisie, puis un comportement de celle-ci est à son tour choisi, le tout aléatoirement. Le sparamètre de types de base ne sont pas donnée, ils seront calculés par la valuation. Les paramètres de types définis par l utilisateur sont choisis au cours de l exécution. Lors de l exécution d une méthode, plusieurs cas se posent. Tout d abord, si aucune instance n existe pour exécuter la méthode, une instance du bon type est créée, puis la méthode est exécutée sur celle-ci. Si l exécution échoue, l instance reste créée, mais n est pas modifiée. Si l exécution réussit, la valuation instancie les paramètres si nécessaires. De même que pour l exécution, la valuation peut échouer. Si c est la cas, l exécution de la méthode est annulée. Une fois toutes ces étapes passées, une autre méthode et un autre comportement sont choisis. Lorsque tous les comportements de toutes les méthodes ont été exécutés au moins une fois, l algorithme s arrête. Une autre possibilité d arrêt est le dépassement du temps donné à l exécution. À l arrêt de l algorithme, des statistiques sont données, notamment pour le pourcentage de couverture des comportements et des méthodes. Cette méthode fonctionne plutôt bien et couvre un bon nombre de comportements sur les exemples testés. Il reste cependant des comportements qui ne sont pas atteints, mais ceci n est pas dû à l algorithme. Imaginons une classe que l on veut tester qui contient un setter qui modifie un attribut booléen nommé bool. La méthode n est composée que d un seul comportement. La valuation du paramètre donnée par JML-TT donnera toujours false au paramètre. Si une méthode ou un comportement a besoin que bool soit à true pour s exécuter, l exécution ne sera jamais possible. Une solution possible aurait été de ne valuer que seulement de temps en temps, ce qui aurait pu permettre de donner la bonne valeur au paramètre du setter. Malheureusement, même si seulement deux méthodes sont exécutées avant la valuation, il est assez fréquent que cette dernière ne finisse jamais. L idée a donc été abandonnée. Au cours des différents tests, seuls les comportements ayant besoin d une certaine valeur pour un attribut de classe, et que cet attribut n est modifié que par des setters, ne sont pas atteints. L animation aléatoire n est pas entièrement aléatoire. En effet, il est possible de parémétrer certains éléments. Tous d abord, il est possible de donner un pourcentage de levée d exceptions. Ensuite, il est possible de donner un taux de réutilisation d instances existantes. Si une méthode prend en paramètre un objet, ce taux est pris en compte pour choisir si il faut créer une nouvelle instance, ou en réutiliser une ancienne.
34 Chapitre 4. Travail réalisé L exécution aléatoire est une alternative valable à la génération de tests abstraits déjà présente dans JML-TT et présente des résultats plutôt bons. 4.3.2 Méta-heuristiques Malheureusement, aucun générateur de tests utilisant de méta-heuristique n a été développé. Le développement du générateur aléatoire a pris beaucoup plus de temps que prévu, je n ai donc pas eu le temps d implémenter de méta-heuristique. La réutilisation des classes d appel au moteur d animation déjà développées posait problème, il a donc fallu que redévelopper une alternative. Il a fallu gérer la sauvegarde des instances et des variables, ce qui n est pas chose aisée. Si le temps n avait pas manqué, la recherche tabou aurait été implémentée. En effet, suite à l étude réalisée en section 4.2, la recherche tabou semblait être la mieux adaptée au problème. Il y a de grandes chances qu elle marche aussi bien que la recherche aléatoire, voire mieux. En effet, la génération aléatoire génère assez souvent des tests redondants, ce que la recherche tabou empêche. 4.4 Expérimentations Les différents tests réalisés pour l animation aléatoire ont été effectués sur divers exemples. Les statistiques sont données sous la forme : nomméthode : nombre de comportements atteints / nombre de comportements total L exemple le plus simple est celui du porte-monnaie électronique. Le code source est donné en annexe A. La couverture de ce modèle est de 100% en un temps très court. Le résultat donné par l animation est le suivant : Stats : b_purse_purse.b_credit_short : 1 / 1 b_purse_purse.b_withdraw_short : 2 / 2 b_purse_purse.b_set_max_short : 1 / 1 b_purse_purse.b_getbalance_void : 1 / 1 b_purse_purse.b_transfer_purse_purse : 1 / 1 b_purse_purse.b_purse_void : 1 / 1 b_purse_purse.b_purse_purse_purse : 1 / 1 Un autre exemple utilisé pour JML-TT a été l exemple de Demoney. Le code source de ce modèle est disponible en annexe B. C est un modèle représentant la communication entre un terminal bancaire et une carte à puce. La couverture de ce modèle est moins bonne et prend plus longtemps. Le modèle contient des setters, ce qui empêche d atteindre certains comportements. L extraction de comportements ne fonctionne pas sur cet exemple. Les statistiques réalisées à la main donnent un taux de courverture d environ 85% en moyenne. Afin de tester toutes les fonctionnalités offertes par JML-TT, d autres exemples ont été utilisés, mais chacun étant un exemple testant une fonctionnalité, leur intérêt est moindre. Ils ne seront donc pas détaillés davantage. 4.5 Synthèse Dans ce chapitre, nous avons vu le travail réalisé au cours de ce stage. Tout d abord, la mise en place d un exemple de familiarisation pour les méta-heuristiques et l étude d adaptabilité de ces métaheuristiques à la génération de tests de JML-TT. Les performances des algorithmes sont aussi détaillées.
Chapitre 4. Travail réalisé 35 La familiarisation a permis de bien prendre en main les différents algorithmes, et ainsi de réaliser l étude de chacun d eux. Cette étude a montré que les méta-heuristiques n étaient pas bien adaptées à la résolution de la problématique posée dans ce mémoire, sauf pour la recherche tabou. Ensuite, nous avons vu les contributions apportées à JML-TT. L animateur aléatoire paramétré a permis de bien comprendre le fonctionnement interne de JML-TT. Enfin, les expérimentations ont montré que l animation aléatoire de modèles permet de couvrir une bonne partie des comportements des méthodes. Seuls quelques comportements particuliers sont laissés de côté.
36 Chapitre 4. Travail réalisé
Chapitre 5 Conclusion Les logiciels qui sont développés de nos jours ont pour beaucoup des rôles critiques. Beaucoup mettent en jeu des sommes gigantesques ou des vies humaines. La confiance que l on a dans ces logiciels doit être maximale. Si le logiciel commet une erreur, les conséquences peuvent être catastrophiques. Afin d éviter le pire, il est nécessaire de les tester. Pour générer des tests automatiquement, le Model-Based Testing est utilisé pour générer des tests à partir d un modèle formel. Le LIFC a développé un animateur symbolique de modèles JML. Cet outil permet de générer des tests abstraits sur le modèle et ainsi d en augmenter la confiance. La génération automatique est actuellement un point faible dans ce logiciel. Il a donc fallu envisager d autres méthodes pour la génération. La piste des méthodes incomplètes a été explorée. Dans un premier, à l aide d une génération aléatoire de tests, puis avec l utilisation de méta-heuristiques. L adaptation des méta-heuristiques au problème posé a été étudiée dans ce mémoire. Tout d abord, chaque algorithme a été testé sur un exemple de familiarisation. Cette prise en main a permis de bien étudier le fonctionnement des algorithmes, et de comparer leurs performances sur l exemple. Dans un second temps, l étude de l adaptabilité des métaheuristiques a montré que seule la recherche tabou semblait applicable pour la génération automatique de tests. En effet, les autres méthodes posent des problèmes, spécialement au niveau des performances. Les méthodes classiques ont été abandonnées pour des raisons de performances, il est donc important que les méthodes utilisées pour les remplacer soient plus performantes. Un générateur de tests aléatoire a été implémenté dans JML-TT. Ce générateur donne des résultats satisfesants. Les temps d exécution sont bons, mais certains comportements d opérations ne sont pas couverts. Bien qu aucune méta-heuristique n ait été implémentée dans JML-TT, leur utilisation n aurait pas permis de combler cette lacune de la génération aléatoire, le problème étant inhérent à l outil. Cependant, le recherche tabou aurait peut-être permis d accélérer la génération des tests des comportements atteignables. Le travail réalisé a néanmoins permis de mettre en évidence que les méta-heuristiques ne semblent pas toutes adaptées à la génération automatique de tests, du moins, pour JML-TT. De plus, la génération aléatoire est une alternative viable à la génération déjà présente dans JML-TT. Il est donc possible que ce module soit intégré au logiciel dans le futur. JML-TT est toujours en développement et n est pas encore prêt à se retrouver sur le marché de la génération de tests. Il doit d abord atteindre sa maturité avant d être commercialisable. Les logiciels critiques étant de plus en plus nombreux, un outil de ce type a vocation à être utilisé par les entreprises, pas seulement par des universitaires.
38 Chapitre 5. Conclusion
Bibliographie [ABC + 02] F. Ambert, F. Bouquet, S. Chemin, S. Guenaud, B. Legeard, F. Peureux, N. Vacelet, and M. Utting. BZ-TT : A tool-set for test generation from Z and B using constraint logic programming. In Proc. of Formal Approaches to Testing of Software, FATES 2002 (workshop of CONCUR 02), pages 105 120, Brnö, République Tchèque, aug 2002. INRIA report. [Abr96] Jean-Raymond Abrial. The B-Book. Cambridge University Press, 1996. [Bau05] [BDL06] [BFJT05a] [BFJT05b] [Dav87] Benoit Baudry. Assemblage testable et validation de composants. PhD thesis, Université de Rennes 1, 2005. Fabrice Bouquet, Frédéric Dadeau, and Bruno Legeard. JML-Testing-Tools, un animateur symbolique de spécifications JML. In AFADL 06, Approches Formelles dans l Assistance au Développement de Logiciels, Paris, France, march 2006. Session outils. Benoit Baudry, Franck Fleurey, Jean-Marc Jézéquel, and Yves Le Traon. From genetic to bacteriological algorithms for mutation-based testing : Research articles. Softw. Test. Verif. Reliab., 15(2) :73 96, 2005. Benoit Baudry, Franck Fleurey, Jean-Marc Jézéquel, and Yves Le Traon. Automatic test case optimization : A bacteriologic algorithm. IEEE Software, 22(2) :76 82, 2005. Lawrence Davis. Genetic Algorithms and Simulated Annealing. Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 1987. [dbkmr04] P. de Boer, D. Kroese, S. Mannor, and R. Rubinstein. method, 2004. A tutorial on the cross-entropy [DS03] Marco Dorigo and Thomas Stützle. The ant colony optimization metaheuristic : Algorithms, applications and advances. In F. Glover and G. Kochenberger, editors, Handbook of Metaheuristics, volume 57 of International Series in Operations Research & Management Science, pages 251 285. Kluwer Academic Publishers, Norwell, MA, 2003. [EK95] [EMC99] Russel Ebenhart and James Kennedy. Particle swarm optimization. In Proceedings of IEEE International Conference on Neural Networks, volume 4, nov 1995. Doron A. Peled Edmund Mellon Clarke, Orna Grumberg. Model Checking. MIT Press, 1999. [Glo89] Fred Glover. Tabu search. ORSA Journal on Computing, 1989. [HGH99] Jin Kao HAO, Philippe GALINIER, and Michel HABIB. Métaheuristiques pour l optimisation combinatoire et l affectation sous contraintes. Revue d intelligence artificielle, 13, 1999. [KGV83] S. Kirkpatrick, C. D. Gelatt, and M. P. Vecchi. Optimization by simulated annealing. Science, Number 4598, 13 May 1983, 220, 4598 :671 680, 1983. [Lag94] Manuel Laguna. A guide to implementing tabu search. Investigacion Operativa, 1994. [Lag02] Manuel Laguna. Global optimization and meta-heuristics. Encyclopedia of Life Support Systems, 2002.
40 BIBLIOGRAPHIE [LBR99] [UL06] Gary T. Leavens, Albert L. Baker, and Clyde Ruby. JML : A notation for detailed design. In Haim Kilov, Bernhard Rumpe, and Ian Simmonds, editors, Behavioral Specifications of Businesses and Systems, pages 175 188. Kluwer Academic Publishers, 1999. Mark Utting and Bruno Legeard. Practical Model-Based Testing - A tools approach. Elsevier Science, 2006. 550 pages, ISBN 0-12-372501-1.
Annexe A Porte monnaie électronique package purse; public class Purse { // public invariant balance >= 0; // constraint balance!= \old(balance); public short balance; private static short max_balance = 32767; /* public normal_behavior requires s >= 0; modifiable balance; ensures balance == (short)(\old(balance) + s); */ public void credit(short s); /* public behavior requires s >= 0; modifiable balance; ensures balance == (short)(\old(balance) - s); signals (NoCreditException) balance == \old(balance) && s > \old(balance); */ public void withdraw(short s) throws NoCreditException ; /* private behavior requires m > 0; assignable max_balance; ensures max_balance == m; */ public void set_max(short m); /* public behavior ensures \result == balance; */ public /* pure */ short getbalance(); /* public normal_behavior requires p!= null; requires p!= this; assignable balance; ensures balance == \old(p.getbalance()); */ public void transfer(purse p);
42 Chapitre A. Porte monnaie électronique /* private normal_behavior assignable balance, max_balance; ensures balance == 0; ensures max_balance == 32767; */ public Purse(); /* private normal_behavior assignable balance, max_balance; ensures balance == 0; ensures max_balance == 32767; */ public Purse(Purse p); }
Annexe B Demoney public class DemoneyJMLTT{ public /* spec_public */ final static byte SD_P1_LAST_BLOCK = 80; // invariant SD_P1_LAST_BLOCK == 80; public /* spec_public */ final static byte SD_P2 = 0; // invariant SD_P2 == 0; public /* spec_public */ final static byte VERIFY_P1 = 00; // invariant VERIFY_P1 == 0; public /* spec_public */ final static byte VERIFY_P2 = 00; // invariant VERIFY_P2 == 0; public /* spec_public */ final static byte DEBIT_KEY_NUM = 1; // invariant DEBIT_KEY_NUM == 1; public /* spec_public */ final static byte CREDIT_KEY_NUM = 2; // invariant CREDIT_KEY_NUM == 2; public /* spec_public */ final static byte ADMIN_KEY_NUM = 3; // invariant ADMIN_KEY_NUM == 3; public /* spec_public */ final static byte KEY_INDEX = 1; // invariant KEY_INDEX == 1; public /* spec_public */ final static byte PUBLIC_LVL = 0; // invariant PUBLIC_LVL == 0; public /* spec_public */ final static byte DEBIT_LVL = 1; // invariant DEBIT_LVL == 1; public /* spec_public */ final static byte CREDIT_LVL = 2; // invariant CREDIT_LVL == 2; public /* spec_public */ final static byte ADMIN_LVL = 3; // invariant ADMIN_LVL == 3; public /* spec_public */ final static short IT_P1_DEBIT = 0; // invariant IT_P1_DEBIT == 0; public /* spec_public */ final static short IT_P1_CREDIT_FROM_CASH = 1; // invariant IT_P1_CREDIT_FROM_CASH == 1; public /* spec_public */ final static short IT_P1_CREDIT_FROM_BANK = 2;
44 Chapitre B. Demoney // invariant IT_P1_CREDIT_FROM_BANK == 2; public /* spec_public */ final static short IT_P2 = 0; // invariant IT_P2 == 0; public /* spec_public */ final static short CT_P1 = 00; // invariant CT_P1 == 0; public /* spec_public */ final static short CT_P2 = 00; // invariant CT_P2 == 0; // invariant PCU_P1 == 0; public /* spec_public */ final static byte PCU_P2_UNBLOCK = 00; // invariant PCU_P2_UNBLOCK == 0; public /* spec_public */ final static byte C_MAC = 01; // invariant C_MAC == 01; public /* spec_public */ final static byte C_MAC_R_MAC = 11; // invariant C_MAC_R_MAC == 11; public /* spec_public */ final static byte EA_P2 = 0; // invariant EA_P2 == 0; public /* spec_public */ final static byte PUT_MAX_BALANCE = 1; // invariant PUT_MAX_BALANCE == 1; public /* spec_public */ final static byte PUT_MAX_DEBIT = 2; // invariant PUT_MAX_DEBIT == 2; public /* spec_public */ final static byte PUT_PIN = 3; // invariant PUT_PIN == 3; public /* spec_public */ final static byte PUT_DATA_P2 = 0; // invariant PUT_DATA_P2 == 0; private /* spec_public */ boolean personnalized = false; private /* spec_public */ byte accesslevel = PUBLIC_LVL; private /* spec_public */ byte authenticatesequence = PUBLIC_LVL; // invariant authenticatesequence >= PUBLIC_LVL && authenticatesequence <= ADMIN_LVL; private /* spec_public */ short transactionsequence = 0; private /* spec_public */ short balance = 0; // invariant balance >= 0; private /* spec_public */ short maxbalance = 0; private /* spec_public */ short maxdebit = 0; private /* spec_public */ OwnerPIN pin; /* normal_behavior assignable personnalized, maxbalance, maxdebit, accesslevel, authenticatesequence, transactionsequence, pin; ensures \fresh(pin) && pin.pin == 0 && pin.tries == 0 && pin.validated == false && pin.defined == false; ensures personnalized == false && maxbalance == 1 && maxdebit == 1 && balance == 0 && accesslevel == PUBLIC_LVL && authenticatesequence == PUBLIC_LVL && transactionsequence == 0; */
Chapitre B. Demoney 45 public DemoneyJMLTT(); /* behavior requires personnalized == false && accesslevel == ADMIN_LVL; { requires P1 == PUT_MAX_BALANCE && P2 == PUT_DATA_P2 && data > 0; assignable maxbalance; ensures maxbalance == data; also requires P1 == PUT_MAX_DEBIT && P2 == PUT_DATA_P2 && data > 0; assignable maxdebit; ensures maxdebit == data; also requires P1 == PUT_PIN && P2 >= OwnerPIN.PIN_MIN_TRY && P2 <= OwnerPIN.PIN_MAX_TRY && data >= 0 && data <= 9999; assignable pin.pin, pin.tries, pin.trieslimit, pin.defined; ensures pin.pin == data && pin.tries == P2 && pin.trieslimit == P2 && pin.defined == true; } */ public void PUT_DATA(byte P1, byte P2, short data) throws IllegalUseException, WrongParameterException; /* behavior requires personnalized == false && accesslevel == ADMIN_LVL && authenticatesequence == PUBLIC_LVL && transactionsequence == 0 && P1 == SD_P1_LAST_BLOCK && P2 == SD_P2; assignable personnalized; ensures personnalized == true; also requires personnalized == true accesslevel!= ADMIN_LVL authenticatesequence!= PUBLIC_LVL transactionsequence!= 0 P1!= SD_P1_LAST_BLOCK P2!= SD_P2; assignable \nothing; signals (Exception) true == true; */ public void STORE_DATA(byte P1, byte P2) throws IllegalUseException, WrongParameterException; /* normal_behavior requires personnalized == true && P1 == DEBIT_KEY_NUM && P2 == KEY_INDEX; assignable authenticatesequence; ensures authenticatesequence == DEBIT_LVL; also requires personnalized == true && P1 == CREDIT_KEY_NUM && P2 == KEY_INDEX; assignable authenticatesequence; ensures authenticatesequence == CREDIT_LVL; also requires P1 == ADMIN_KEY_NUM && P2 == KEY_INDEX; assignable authenticatesequence; ensures authenticatesequence == ADMIN_LVL; also requires P2!= KEY_INDEX ( (personnalized == false && (P1!= DEBIT_KEY_NUM P1!= CREDIT_KEY_NUM)) &&
46 Chapitre B. Demoney P1!= ADMIN_KEY_NUM); assignable authenticatesequence; ensures authenticatesequence == PUBLIC_LVL; */ public void INITIALIZE_UPDATE(byte P1, byte P2) throws IllegalUseException, WrongParameterException; /* behavior requires (P1 == C_MAC P1 == C_MAC_R_MAC) && P2 == EA_P2 && authenticatesequence!= PUBLIC_LVL; assignable accesslevel, authenticatesequence, transactionsequence; ensures accesslevel == \old(authenticatesequence) && authenticatesequence == PUBLIC_LVL && transactionsequence == 0; also requires (P1!= C_MAC && P1!= C_MAC_R_MAC) P2!= EA_P2; assignable accesslevel, authenticatesequence, transactionsequence; signals (WrongParameterException) accesslevel == \old(accesslevel) && authenticatesequence == \old(authenticatesequence) && transactionsequence == \old(transactionsequence); also requires authenticatesequence == PUBLIC_LVL; assignable accesslevel, authenticatesequence, transactionsequence; signals (WrongParameterException) accesslevel == \old(accesslevel) && authenticatesequence == \old(authenticatesequence) && transactionsequence == \old(transactionsequence); */ public void EXTERNAL_AUTHENTICATE(byte P1, byte P2) throws IllegalUseException, WrongParameterException; /* behavior requires personnalized == true && P2 == IT_P2 && authenticatesequence == PUBLIC_LVL && transactionsequence == 0; requires P1 == IT_P1_DEBIT && accesslevel == DEBIT_LVL && data > 0 && data <= maxdebit && data <= balance && transactionsequence == 0; assignable transactionsequence; ensures transactionsequence == (- data); also requires personnalized == true && P2 == IT_P2 && authenticatesequence == PUBLIC_LVL && transactionsequence == 0; requires P1 == IT_P1_CREDIT_FROM_CASH && accesslevel == CREDIT_LVL && data > 0 && (data + balance) <= maxbalance; assignable transactionsequence; ensures transactionsequence == data; also requires personnalized == true && P2 == IT_P2 && authenticatesequence == PUBLIC_LVL && transactionsequence == 0; requires P1 == IT_P1_CREDIT_FROM_BANK && accesslevel == CREDIT_LVL && data > 0 && (data + balance) <= maxbalance && pin.validated == true && (balance + data) <= maxbalance; assignable transactionsequence, pin.validated; ensures transactionsequence == data && pin.validated == false;
Chapitre B. Demoney 47 */ public void INITIALIZE_TRANSACTION(byte P1, byte P2, short data) throws IllegalUseException, WrongParameterException; /* normal_behavior requires personnalized == true && P1 == CT_P1 && P2 == CT_P2 && transactionsequence!= 0; assignable balance, transactionsequence; ensures balance == \old(balance) + \old(transactionsequence) && transactionsequence == 0; */ public void COMPLETE_TRANSACTION(byte P1, byte P2) throws IllegalUseException, WrongParameterException; /* behavior requires personnalized == true && accesslevel == CREDIT_LVL && P1 == VERIFY_P1 && P2 == VERIFY_P2 && pin.defined == true && pin.tries!= 0 && data >= 0 && data <= 9999; requires data == pin.pin; assignable pin.validated, pin.tries; ensures pin.validated == true && pin.tries == pin.trieslimit; also requires personnalized == true && accesslevel == CREDIT_LVL && P1 == VERIFY_P1 && P2 == VERIFY_P2 && pin.defined == true && pin.tries!= 0 && data >= 0 && data <= 9999; requires data!= pin.pin; assignable pin.tries; ensures pin.tries == \old(pin.tries) - 1; */ public void VERIFY_PIN(byte P1, byte P2, short data) throws IllegalUseException, WrongParameterException; } /* normal_behavior requires personnalized == true && P1 == PCU_P1; { requires P2 == PCU_P2_UNBLOCK && accesslevel == ADMIN_LVL && pin.tries == 0; assignable pin.tries, transactionsequence, authenticatesequence; ensures pin.tries == pin.trieslimit && transactionsequence == 0 && authenticatesequence == PUBLIC_LVL; also requires (P2 >= OwnerPIN.PIN_MIN_TRY && P2 <= OwnerPIN.PIN_MAX_TRY) && data >= 0 && data <= 9999 && accesslevel == ADMIN_LVL && pin.tries > 0; assignable pin.pin, pin.tries, pin.trieslimit, transactionsequence, authenticatesequence; ensures pin.pin == data && pin.trieslimit == P2 && pin.tries == pin.trieslimit && transactionsequence == 0 && authenticatesequence == PUBLIC_LVL; } */ public void PIN_CHANGE_UNBLOCK(byte P1, byte P2, short data) throws IllegalUseException, WrongParameterException; class OwnerPIN { // invariant pin >= 0 && pin <= 9999; static /* spec_public */ short pin; // invariant PIN_MIN_TRY == 3 && PIN_MAX_TRY == 15;
48 Chapitre B. Demoney final static byte PIN_MIN_TRY = 3; final static byte PIN_MAX_TRY = 15; // invariant trieslimit >= PIN_MIN_TRY && trieslimit <= PIN_MAX_TRY; static /* spec_public */ byte trieslimit = 3; } // invariant tries >= 0 && tries <= trieslimit; static /* spec_public */ short tries = 0; static /* spec_public */ boolean validated, defined; class IllegalUseException extends Exception { } class WrongParameterException extends Exception { }