Pratique Des Tests Logiciels - Jean-Francois Pradat-Peyre
Pratique Des Tests Logiciels - Jean-Francois Pradat-Peyre
Pratique Des Tests Logiciels - Jean-Francois Pradat-Peyre
ISBN : 9782100772483
Toute reproduction d'un extrait quelconque de ce livre par quelque procédé que ce soit, et notamment
par photocopie ou microfilm, est interdite sans autorisation écrite de l'éditeur.
Avant-propos
Suppléments en ligne
Retrouvez sur www.dunod.com les suppléments en ligne qui complètent cet
ouvrage. Il s’agit d’exercices corrigés, du corrigé commenté du QCM, de
compléments sur les tests unitaires avec JUnit, des informations de mise à
niveau, des applications avancées, des logiciels recommandés, des supports
didactiques...
Notes
[1] On pourra également penser aux énormes soucis humains créés par le
dysfonctionnement du logiciel de gestion de paie de l’armée Louvois mis en
place en 2011.
Chapitre 1
Dans tous ces cas, une erreur de réalisation ou de conception d’un logiciel
conduit un système automatique à faire autre chose que ce pourquoi il est fait.
Différents termes sont employés pour relater ces problèmes et il nous faut
préciser ici ce que l’on entend par erreur, défaut, défaillance ou panne.
Il faut noter que seul un modèle précis, et donc difficile à produire, permet
d’essayer de calculer a priori ces indicateurs. Le plus souvent ils sont estimés
à l’aide d’anciens logiciels ou des logiciels concurrents, ce qui donne une
idée de ce qui est possible, et ils sont affinés en fonction des exigences
métiers, ce qui donne un objectif de ce qu’il faudrait atteindre.
Comme les besoins en termes de logiciels évoluent plus vite que les
possibilités théoriques de construction sûre, l’approche 2 est celle qui est
choisie dans la majorité des cas. Il faut donc accepter que le logiciel produit
contienne des défauts ; l’enjeu est alors d’éliminer les défauts qui conduisent
à des défaillances avant que le logiciel entre en service. Si nécessaire, il
faudra également s’assurer que le logiciel satisfait un certain nombre de
normes légales ou contractuelles, nécessaires par exemple pour une
certification du logiciel. Enfin, une fois le logiciel déployé, il faudra, lors des
phases de maintenance, vérifier que les évolutions ou améliorations n’ont pas
entamé les parties non modifiées du logiciel et, dans le cas de maintenances
correctives, que la nouvelle version corrige bien les défauts de l’ancienne.
Toutes ces étapes sont réalisées à l’aide de différents types de tests : tests
unitaires ou tests d’intégration lors des phases de développement, tests
système et tests de validation lors des phases de déploiement, tests
d’acceptation et tests de recette lors des phases d’acceptation ou de
certification, et enfin tests de correction et de non-régression lors des phases
de maintenance.
On le voit, les objectifs des tests peuvent être multiples ; cependant, en aucun
cas, et même si cela est choquant, il faut être conscient que leur but n’est pas
d’éliminer tous les défauts. L’objet principal est d’analyser le comportement
d’un logiciel dans un environnement donné : ainsi, il ne sert a priori à rien de
tester le bon fonctionnement de la gestion du système de freinage d’une
automobile pour une vitesse de 1 000 km/h.
Par ailleurs, les tests vont permettre de découvrir un certain nombre d’erreurs,
mais il est faux de penser que l’amélioration de la fiabilité est proportionnelle
au nombre de défauts détectés puis corrigés. En effet, un logiciel mal conçu,
en particulier du point de vue de son architecture, va faire apparaître un grand
nombre de défauts. La correction de ces défauts pourra avoir pour
conséquence, non de réduire leur nombre, mais au contraire d’en créer de
nouveaux. Si le ratio entre le nombre de défauts créés et le nombre de défauts
corrigés est supérieur à 1, les tests ne cesseront de découvrir des défauts sans
pour autant qu’il y ait convergence vers un logiciel fiable. De même, tester
beaucoup en quantité et en temps n’est pas nécessairement un gage de
qualité. Exercer sans cesse le même code avec des jeux de valeurs similaires
ne donne aucune garantie quant à la capacité à découvrir des défauts de
comportement. Tester est une activité complexe qui demande expérience et
méthode et nous allons essayer maintenant de cerner les quelques principes
qui doivent guider toute activité de test.
Aussi, peu à peu est apparu le besoin de mener les projets de façon plus
souple en remettant régulièrement des versions de plus en plus complètes de
l’application au client, ce qui revient à replacer implicitement ou
explicitement le client et les tests au cœur du processus : c’est l’apparition
des méthodes de développement itératif et incrémental qui se déclinent sous
de nombreuses formes. On peut citer la méthode RAD (Rapid Application
Development) la méthode UP (Unified Process) ou la méthode XP (Extreme
Programming) et ses variantes du développement dit agile. On notera que
tous ces modèles dérivent du cycle en V, sauf peut-être certains modèles
itératifs extrêmes (comme la méthode Crystal clear), et que donc la
compréhension du cycle en V est la condition nécessaire pour la
compréhension des autres modèles.
Pour cela nous détaillons maintenant, sous l’angle des tests, les différentes
phases du cycle en V et en particulier les phases de conception qui, bien que
n’incluant pas directement des activités de test, sont des phases primordiales
pour la capacité de mener à bien ces tests une fois les étapes de réalisation
finies.
On notera que, dans une approche UML, les phases d’expression et d’analyse
du besoin pourront utiliser les cas d’utilisation du système et la modélisation
des processus métiers pour faciliter la préparation des tests.
Grâce aux méthodes agiles, le client est au cœur de son projet et obtient très
vite une première mise en production de son logiciel. Il est ainsi possible
d’associer les utilisateurs dès le début du projet. Nous présentons maintenant
brièvement les caractéristiques essentielles de deux modèles significatifs et
très employés : UP pour « Unified Process » et XP pour « eXtreme
Programming ».
Il est important de noter que tous ces modèles, UP compris, mis entre des
mains inexpérimentées donneront des résultats catastrophiques qui ne seront
pas imputables aux modèles, mais plutôt à ceux qui les mettent en œuvre.
Comme l’a dit B. Boehm, il a bien longtemps : « The method won’t save you,
but it can help ! ». Avant d’utiliser un modèle quelconque, nous devons
comprendre il marche (cf. Coût et durée des projets informatiques de J. Printz
chez Hermes, chapitre 1).
Les tests s’intègrent dans chaque itération, ce qui permet un contrôle continu
de la qualité. Leur mise en œuvre est simplifiée car la méthode s’appuie sur
des modèles et des cas d’utilisations qui permettent de dériver simplement
des cas de tests fonctionnels pertinents.
Dans l’approche XP, la « software factory » distingue six phases, que l’on
pourrait comparer aux phases de UP. L’ensemble du dispositif est alimenté à
partir de cas d’emplois élaborés en coopération avec le client. Les cas
d’emploi sont la base de la relation avec le client.
La phase d’exploration, comme son nom l’indique est une phase exploratoire
dont le but est de cerner les grandes lignes du système à réaliser.
La phase de planification, dite aussi phase de « planning game », est faite en
collaboration avec le client, et K. Beck va jusqu’à dire : « you will have to
educate your customer about the rules of the game », ce qui a toutes les
apparences d’un vœu pieux. Toutes les deux semaines, et pour les deux
prochaines semaines, les développeurs estiment le coût des fonctionnalités
candidates, et le client choisit les fonctionnalités à implémenter en fonction
du coût et de la valeur ajoutée. La durée pour réaliser ce qui constitue la
première version ou release ne doit pas excéder 6 mois, car au-delà le risque
de divergence est jugé excessif ; a contrario, cela donne une indication sur la
taille des projets envisagés qui sont des petits, voire très petits, projets.
La phase d’itération livre une succession d’incréments, dont le premier doit
mettre en place l’architecture du logiciel (The whole system in a skeletal
form) ce qui peut nécessiter de « bouchonner » au maximum les composants
qui seront délivrés ultérieurement.
Ceci n’est possible que si le chef de projet-architecte a une grande
expérience. Le client est mis à contribution pour les tests fonctionnels
correspondant à chacun des incréments livrés.
La phase de mise en production, correspondant à la première release du
système, correspond à la fin du « planning game », avec des itérations
extrêmement courtes de l’ordre de 1 semaine, au lieu des 3 ou 4 d’une
itération nominale. K. Beck nous dit que c’est le « perfect time to tune », ce
qui est pour le moins ambigu ; cela présuppose que la performance a été
intégralement anticipée lors de la première itération qui définit l’architecture.
C’est une nouvelle évidence que cela ne peut marcher qu’avec du personnel
très expérimenté ; dans ce cas seulement on n’aura à effectuer que quelques
réglages dans la phase de mise en production. Avec une équipe
inexpérimentée, le risque de dérapage est maximum.
La phase de maintenance, considérée comme l’état normal d’un projet XP,
dure aussi longtemps qu’il y a des besoins nouveaux à satisfaire. L’équipe XP
a en charge la maintenance de la release en cours d’exploitation et de
développement de la prochaine release dans laquelle seront intégrées les
corrections dues aux défaillances constatées. Implicitement l’équipe XP fait
l’ingénierie complète au sens ISO 12207.
La phase de terminaison marque l’arrêt du projet XP qui n’est pas forcément
l’arrêt de l’exploitation. La terminaison nominale correspond à la satisfaction
de tous les besoins, ce qui laisse présager une période d’exploitation de
plusieurs années, sans modification. C’est comme le dit K. Beck « The time
to write a 5 to 10 pages tour of the system », ce qui est pour le moins léger,
pour ne pas dire irresponsable car cela revient à dire que la documentation,
c’est le code !
La terminaison par « mort par entropie » (Entropic death) correspond à la
situation où l’architecture initiale n’ayant pas résisté aux chocs successifs des
besoins à satisfaire ces derniers ont fini par la détruire ; il est plus sage
d’arrêter le projet. Le projet succombe à l’excès de complexité qu’il a lui-
même créé, et de laquelle rien n’émerge, que du chaos. Dans ce cas, il est
recommandé que le client et l’équipe XP qui ont opéré de concert tirent les
leçons de l’échec pour envisager un avenir meilleur en faisant un bilan de
projet.
Sans vouloir gloser sur la terminologie, K. Beck nous dit que « XP is not
magic. Entropy eventually catches XP projects, too. You just hope that it
happens much later than sooner ». Cela fait du développement un pari plutôt
qu’un acte d’ingénierie raisonné et assumé. Ceci n’est jouable que pour de
petits projets, sans enjeu stratégique véritable pour l’entreprise. La
complexité est toujours un vrai risque, et aucune méthode ne garantit le
succès.
Pour cela on prévoira des tests qui couvrent dans un premier temps les cas
nominaux afin de vérifier que le code chargé de traiter le comportement
normal du composant est réalisé correctement. Ensuite, il faudra prévoir des
tests pour vérifier que la prise en compte des cas irréguliers est correcte. Avec
ces deux premières catégories de tests, on aura testé (au sens de la recherche
d’erreurs) que le composant fait ce pourquoi il est prévu. Après cela il faudra
tester que le composant réagit correctement lorsqu’on lui passe des données
clairement hors du domaine de définition et enfin lorsqu’on lui passe des
données limites au sens de ces domaines ; c’est en effet pour ces valeurs
limites qu’apparaissent fréquemment des erreurs de codage ou
d’interprétation des spécifications.
En procédant de la sorte, on découvre en premier lieu les erreurs ou oublis sur
ce que doit faire le composant puis on découvre les erreurs concernant le
traitement des cas pathologiques et enfin on vérifie que le composant est
protégé (selon ce qui est défini) contre une utilisation anormale.
Ces tests seront sous la responsabilité du développeur ou de l’équipe en
charge des tests au sein de l’équipe projet.
2.4.2. Le test d’intégration
L’intégration est fondamentalement une activité de test et de planification de
test, au sens large du terme. La première difficulté de l’intégration est qu’elle
est totalement tributaire de la qualité des activités et des processus amont.
Selon le modèle d’estimation COCOMO (acronyme de « Constructive COst
MOdel »), l’intégration consomme de 20 à 35 % de l’effort total de
développement, selon la complexité du projet.
Les tests d’intégration se concentrent sur les interfaces entre les composants,
les interactions entre les différentes parties du système ou les interfaces entre
systèmes (pour les fédérations de systèmes). Il est important de noter que l’on
ne teste pas le résultat de l’ensemble (ce qui sera le rôle des tests de
validation) mais que l’on se concentre sur les échanges au niveau des
interfaces. Il va sans dire que l’impact des choix architecturaux est très
important à cette étape car, sans interface clairement définie, pas de tests
d’intégration possibles !
Une application étant généralement constituée de multiples composants, et les
interactions entre ceux-ci pouvant être complexes il va falloir choisir dans
quel ordre réaliser ces tests d’intégrations. Si l’on considère l’exemple
suivant (figure 2.14), le composant 2 utilise les composants 4 et 5 et est
utilisé par le composant 1.
Fig. 2.14 Un schéma d’architecture logicielle
Selon l’ordre dans lequel ces quatre composants seront intégrés il sera plus
ou moins facile de tester leurs interactions. On distingue généralement, trois
stratégies :
La stratégie du Big Bang
La stratégie par lots fonctionnels
Les stratégies incrémentales
Lorsque les tests se concentrent sur la découverte de défauts par rapport aux
spécifications ou exigences fonctionnelles, on emploie le terme de « tests de
validation ». Lorsque les tests se focalisent sur la découverte de défauts
concernant des spécifications non fonctionnelles, on parle plutôt de « tests
système » même si ce terme est également utilisé pour des tests de validation
à partir du moment où ils sont effectués dans un environnement système
proche ou identique au système cible.
Dans tous les cas (petits ou gros projets), les tests de validation seront menés
par une équipe indépendante qui cherchera à obtenir une couverture
maximale des différentes fonctionnalités et cas d’utilisation. Pour cela, on
aura recours à des outils, qui peuvent être un simple tableur, pour garantir
cette exhaustivité et assurer une traçabilité des tests vis-à-vis des exigences
exprimées lors de la phase de spécification.
Pour la validation d’une fédération de systèmes, les choses sont légèrement
plus complexes car les phases d’intégration et de validation vont s’alterner
dans une progression qui se calque sur le niveau de granularité, du plus fin au
plus gros : composants logiciels, application, système, fédération de
systèmes.
Fig. 2.16 Étapes d’intégration et de validation pour une fédération de systèmes
Dans le cas de logiciel à forte diffusion, on désignera par « tests alpha » des
tests d’acceptation réalisés par une équipe différente de l’équipe de
développement mais de la même entreprise. Ces tests permettront à l’équipe
de développement d’avoir un premier retour sur le logiciel en situation réelle
par un client plus tolérant et moins vindicatif en cas de problèmes importants
qu’un client réel. Une fois cette étape d’acceptation passée, l’entreprise
pourra passer aux « tests bêta » qui reproduisent cette mise en situation mais,
cette fois-ci, avec de « vrais » clients qui auront accepté d’être cobayes en
échange de la possibilité d’utiliser des versions récentes et non encore
commercialisées d’un logiciel donné. Ces clients sont alors dénommés
« bêta-testeurs ». Ils participent alors volontairement et sans coûts pour la
société éditrice du logiciel à l’amélioration de celui-ci. Leur motivation est
soit l’envie de posséder une version récente d’un logiciel à la mode, soit le
besoin de passer à une version qui permet de sortir d’une impasse en
corrigeant ou en apportant une fonctionnalité par rapport à la version
précédente, soit encore l’envie de participer d’une façon peu contraignante au
développement d’un logiciel libre. Le schéma suivant résume le
positionnement des différents tests étudiés dans les phases de développement
ainsi que les documents et données en entrées de chacun.
Fig. 2.17 Positionnement des types de tests dans un cycle en V
2.6. Conclusion
Comme nous l’avons vu, les tests peuvent (et doivent) être un guide lors des
différentes phases d’un projet logiciel et ceci quel que soit le cycle de
développement retenu, qu’il soit « classique » avec le cycle en V ou « agile »
avec les méthodes itératives et incrémentales. Habilement préparés et
intelligemment prévus lors des phases de conception, ils seront plus simples à
concevoir et à exécuter durant les phases de réalisation, de déploiement ou de
maintenance.
Le point crucial est la définition d’une architecture testable permettant à la
fois un développement de composants fiables, simples à intégrer et rendant
possible l’automatisation du jeu et des tests lors des différentes phases de
déploiement. En effet, sans automatisation, l’effort consacré aux tests sera
grand lors des phases initiales de développement, ce qui peut être acceptable
au vu de l’amélioration de la qualité constatée, mais deviendra pesant au fil
des releases sans réelle justification.
Le succès actuel des démarches agiles est sans aucun doute dû en grande
partie à cette insertion au cœur du développement de l’automatisation des
tests.
Notes
[1] Voir, par exemple le chapitre 4 de l’ouvrage Architecture logicielle -
Concevoir des applications simples, sûres et adaptables de J. Printz.
[2] Voir par exemple l’article de Yves Le Traon et de Benoit Baudry « Test
d’intégration d’un système à objets - planification de l’ordre d’intégration »
dans les actes de la conférence LMO 2006, pages 217-230.
[3] Middleware orienté messages ou « Messages Oriented Middleware ».
[4] Un fichier de log ou « Log File » est un fichier qui regroupe,
généralement de façon chronologique, les différents événements survenus au
niveau d’une application ou d’un système.
Chapitre 3
Et enfin, dans les deux cas, ces techniques cherchent à découvrir des erreurs !
Il sera donc profitable de combiner autant que possible ces deux types
d’approche des tests qui sont complémentaires.
Cette restriction est souvent volontaire, c’est-à-dire que le testeur dispose des
sources, les comprend, mais s’en interdit l’utilisation afin de ne pas se laisser
influencer lors du choix des valeurs de test par la façon dont le logiciel a été
réalisé. En effet, le code peut être vu comme une écriture possible du
raisonnement mené par le programmeur ou par l’équipe de spécification pour
répondre au problème posé. Si le raisonnement est incorrect mais
convaincant, le testeur peut se trouver à son tour dans l’erreur. En
s’interdisant l’utilisation des sources, le testeur travaille moins sous influence
de ces raisonnements initiaux et pourra découvrir des erreurs subtiles de
conception.
Dans ce contexte, toute spécification peut être utilisée : spécifications
informelles, comme un document en langue naturelle, spécification formelle,
comme un automate ou un réseau de Petri, spécifications semi-formelles,
comme un schéma UML ou un « use case ». Il est évident que plus les
spécifications seront précises, plus elles pourront être utilisées avec profit.
Ces spécifications peuvent décrire le format des interfaces (types et nombre
des paramètres, type du ou des résultats), les résultats et comportements
attendus pour des valeurs conformes aux spécifications des interfaces ou,
mais cela est malheureusement non systématique, les résultats et
comportements attendus pour des valeurs non conformes aux spécifications
des interfaces.
Nous détaillerons au chapitre suivant les techniques Boîte noire les plus
utilisées :
Tests par partitionnement des domaines
Tests par utilisation de graphe causes-effets
Tests aux limites
Tests à l’aide de diagrammes états/transitions
Mais avant cela, examinons brièvement, les tests Boîte blanche, techniques
complémentaires des tests boîte noire.
où, aveV est le volume moyen par module, aveCCe est la complexité
cyclomatique moyenne par module, aveLOC est le nombre moyen de
lignes par module et perCM est le pourcentage moyen de lignes de
commentaires par module
Plus la valeur est élevée plus le logiciel est facilement maintenable. Les
seuils admis sont :
85 : maintenance facile
65-85 : maintenance correcte
< 65 : difficulté à maintenir
Selon le contexte, on intègre ou non la part relative aux commentaires
(MIcomment). Les constantes doivent être ajustées à chaque projet en
particulier celles relatives aux commentaires :
L’indice de maintenabilité peut être utilisé pour
L’analyse de l’évolution des versions d’un logiciel dans le temps
L’analyse de l’uniformité du codage au sein d’un même projet
Mettre en rapport différents projets réalisés dans des contextes
comparables
Exemple : l’indice de maintenabilité du noyau et des programmes
utilisateurs de FreeBSD diminuent avec les années (mais faiblement)[3].
On remarquera que l’on peut artificiellement augmenter la valeur de
l’indice de maintenabilité sans pour autant améliorer la qualité du
logiciel. Il suffit, par exemple, de découper les gros modules en plus
petits ou d’ajouter artificiellement des commentaires
Les inspections sont les revues les plus formelles, mais, toute forme de revue
bénéficiera d’un peu de formalisation, en particulier, on prendra garde à :
Définir des critères précis pour le choix des personnes impliquées
Définir des règles précises pour la conduite des réunions
Terminer la revue sur un consensus
Garder trace du processus mis en place et chercher à l’améliorer sans
cesse
3.6. Conclusion
Différentes stratégies complémentaires peuvent être mises en œuvre pour
tester un logiciel. Chacune se concentre plus particulièrement sur certains
objectifs comme la mise en évidence de défauts dans la réalisation, la
vérification de la cohérence ou du caractère complet des spécifications, le
respect de standards, etc. Chacune déploiera ses propres méthodes et sera
supportée par des outils dédiés comme l’aide à l’exécution, l’aide à la
collecte de données, la vérification structurelle, etc. Cependant, toutes ces
stratégies partagent un certain nombre de points communs comme
l’impossible exhaustivité, la qualité de la réflexion et l’importance des
relations humaines dans tous les processus liés au test. En cela, la formation
et l’expérience du testeur sont irremplaçables.
Notes
[1] T. J. McCabe, « A Complexity Measure », IEEE Transactions on
Software Engineering, vol. 2, N° 4, pp. 308-320, July 1976.
[2] M. H. Halstead, Elements of Software Science (Operating and
Programming Systems Series), Elsevier Science Inc. NY, 1977.
[3] Don Coleman, Dan Ash, Bruce Lowther, and Paul Oman, « Using
metrics to evaluate software system maintainability », Computer, 27(8) :44–
49, 1994.
[4] S.R. Chidamber and C.F. Kemerer, « A Metrics Suite for Object Oriented
Design », IEEE Trans. Software Eng., vol. 20, no. 6, pp. 476-493, 1994.
[5] Littéralement : « contrôle de copain ».
[6] Littéralement : « passage à travers ».
[7] Fagan, Michael E : « Design and Code Inspections to Reduce Errors in
Program Development », IBM Systems Journal, Vol. 15, No. 3, 1976.
Chapitre 4
4.1.2. Illustration
Considérons le problème suivant : « Comment tester efficacement un
composant logiciel qui génère des scripts utilisés pour valider le
comportement de serveurs web ». Ce composant prend quatre paramètres en
entrée et génère un script adapté à ces valeurs de paramètres. Ces paramètres
sont :
Le type du poste client à partir duquel le script sera exécuté ; les valeurs
possibles sont « Linux Type1 », « Solaris », « Windows Me », « Vista »,
« Mac Os X », soit cinq valeurs distinctes.
Le type du navigateur client ; les valeurs possibles sont « FireFox »,
« Internet Explorer » et « Netscape ».
Le type de données téléchargées ; les valeurs possibles sont « jpeg »,
« avi », « mp3 » et « wave ».
Le fait d’utiliser une connexion sécurisée ou non.
Windows Internet
Jpeg sécurisée
Me Explorer
Windows
Avi Netscape non sécurisée
Me
Windows
Wave FireFox sécurisée
Me
Windows Internet
Mp3 non sécurisée
Me Explorer
Mac Os
Jpeg FireFox sécurisée
X
Mac Os Internet
Avi non sécurisée
X Explorer
Mac Os
Wave Netscape sécurisée
X
Mac Os
Mp3 FireFox non sécurisée
X
Internet
Solaris Jpeg non sécurisée
Explorer
Solaris Avi Netscape sécurisée
Solaris Wave FireFox non sécurisée
Internet
Solaris Mp3 sécurisée
Explorer
Une fois la table construite, le testeur peut supprimer des cas impossibles
(sauf s’il souhaite vérifier le comportement du composant dans le traitement
des cas impossibles). Il peut, bien entendu, également ajouter des tests qui lui
sembleraient manquer : la méthode ne se substitue jamais à l’intelligence du
testeur qui doit se souvenir que le problème est difficile et que son expertise
et son expérience peuvent permettre, mieux que toute méthode, de trouver
des jeux de tests plus pertinents.
4.1.3. Commentaires
Cette technique est très efficace, simple à mettre en œuvre et produit des
documents de test facilement réutilisables. De plus, elle peut être utilisée à
travers d’autres méthodes pour générer des jeux de valeurs à partir de cas de
test. Malheureusement elle ne peut pas s’appliquer à tout type de composant
logiciel. En effet, la plupart des paramètres prennent leurs valeurs dans des
ensembles de cardinal très élevé (de l’ordre de 2n, où n est la taille en bits de
la représentation binaire des valeurs). Le nombre de cas de test étant
dépendant de ces cardinaux, cette méthode ne pourra pas être appliquée
directement. Il faut donc trouver d’autres stratégies ; c’est l’objet des sections
suivantes.
Dans le cas où les variables d’entrées sont liées entre elles, les classes
d’équivalence peuvent également être construites, mais il sera plus difficile
d’obtenir une réelle partition du domaine d’entrée ; certaines valeurs pourront
appartenir à plusieurs classes d’équivalences. Cela n’a pas de réelles
conséquences si ce n’est peut-être de générer plus de cas de tests que
nécessaire.
On notera que les règles 4) et 5) sont des règles très générales et peuvent se
substituer à toutes les autres. On notera également que le fort typage des
langages modernes peut interdire certains choix ; par exemple, une fois un
type énuméré défini, il est impossible en Ada ou en Java de prendre une
valeur autre que celles permises par la définition du type.
4.2.4. Illustration
Supposons que l’on ait à tester une fonction Lendemain qui calcule le
lendemain d’une date passée en paramètre et définie par trois entiers : Jour,
Mois, et Année. On considère (arbitrairement) que l’année doit être plus
grande que 1582 (année de mise en place du calendrier Grégorien en France)
et plus petite que 3000. On doit prendre en compte les années bissextiles. La
définition d’une année bissextile est « Toutes les années dont le millésime est
divisible par quatre sont des années bissextiles, sauf les années séculaires
dont le millésime n’est pas divisible par quatre cents. Le nombre de jours du
mois de février des années bissextiles compte vingt-neuf jours »[1]. L’année
1700, qui est bissextile en France, ne l’est pas forcément partout car le
calendrier Grégorien ne s’est mis que progressivement en place à travers le
monde (par exemple en 1752 au Royaume-Uni et dans ses colonies sur la
côte est de l’Amérique du Nord, et de l’actuel nord-ouest des États-Unis).
Construisons maintenant des jeux de tests pour la fonction Lendemain en
utilisant la technique des classes d’équivalence. Pour cela on commence par
prendre en compte les contraintes élémentaires sur les paramètres en entrées.
Puis on affinera ces contraintes pour enfin prendre en compte les contraintes
liant les entrées et les sorties.
Nous avons ainsi quatre classes d’équivalence valides pour les jours, quatre
pour les mois et trois pour les années. Nous avons deux classes d’équivalence
invalides pour les jours, deux pour les mois et deux également pour les
années.
Du fait que les paramètres en entrée sont liés, certaines combinaisons de
valeurs valides conduisent à des entrées invalides ; par exemple, le 29 février
2004 ou le 31 avril 1997 sont deux dates invalides alors que chaque valeur
composant la date prise individuellement est valide. Les spécifications
permettent ici de détecter ce problème très facilement ; ce n’est pas toujours
le cas.
Nous pouvons maintenant générer les jeux de tests. Si l’on se contente du
critère « All Singles », quatre valeurs de test permettent de tester toutes les
classes valides (tableau 4.6).
À cela, il faut ajouter des jeux de tests correspondant aux classes invalides ;
on prend garde ici à ne couvrir qu’une seule classe invalide à la fois afin de
bien tester individuellement l’effet de l’entrée invalide.
Si l’on veut tester plus à fond la fonction Lendemain, le testeur peut choisir la
stratégie all pairs pour générer les jeux de valeurs de test. Cela conduit à
générer 4*4 = 16 jeux de valeurs distincts (tableau 4.8).
Tab. 4.8 Jeux de tests selon le critère all pairs pour la fonction Lendemain
Classes Classes Classes
Jeux de
d’équivalence d’équivalence d’équivalence
valeur
« Jour » « Mois » « Année »
Jour ∈ [1, 28] Mois ∈ {1, 3, 5, Année bissextile (14, 5,
7, 8, 10} 2004)
Année non (29, 12,
Jour = 29 Mois = 12
bissextile 1999)
Mois ∈ {4, 6, 9, (30, 6,
Jour = 30 Année = 2000
11} 2000)
(31, 2,
Jour = 31 Mois = 2 Année bissextile
1916)
(8, 12,
Jour ∈ [1, 28] Mois = 12 Année bissextile
1888)
Mois ∈ {4, 6, 9, Année non (29, 9,
Jour = 29
11} bissextile 1805)
(30, 2,
Jour = 30 Mois = 2 Année = 2000
2000)
Mois ∈ {1, 3, 5, (31, 3,
Jour = 31 Année bissextile
7, 8, 10} 2012)
À ces jeux de tests, il convient d’ajouter ceux du tableau concernant la prise
en compte des classes invalides. On obtient alors une qualité des jeux de tests
qui laisse peu d’espoir à une erreur de rester inaperçue ! On remarquera que
le jeu de tests correspondant au calcul du lendemain du 31 décembre 2000 est
bien construit par cette méthode, mais qu’il faudrait rajouter manuellement le
test du lendemain des 28 et 29 février 2000.
4.2.5. Commentaires
Cette stratégie de construction de jeux de tests est un des piliers de toute
méthode visant à rendre praticable le test de logiciels réels. L’effort à fournir
est un effort d’abstraction et d’analyse des spécifications. Cet effort permet
donc à la fois de rendre plus efficaces les tests, dans le sens où il permettra de
considérer un seul cas là où, sans cette abstraction, plusieurs cas équivalents
auraient été produits mais, de plus, il permet de détecter un certain nombre
d’incohérences ou d’oublis au niveau des spécifications en portant sur celles-
ci un regard différent de celui du programmeur ou de l’utilisateur.
Le plus classique est néanmoins d’utiliser la technique des tests aux limites
lors des tests unitaires en Boîte noire comme complément du test par
partitionnement. Dans ce cas, les frontières se définissent grâce aux domaines
obtenus lors du calcul des classes d’équivalence :
par la spécification des variables d’entrée,
par la spécification des résultats.
Tab. 4.9 Test aux limites d’une fonction manipulant des numéros de départements
1, 2, 48, 94,
Entrées valides [ 1 – 95 ] 1, 48, 95
95
Cette technique peut également être employée sur des composants dont les
entrées ne sont pas des valeurs numériques. Par exemple, une fonction qui
attend « oui » ou « non » pourra être testée de la façon suivante :
Tab. 4.10 Test aux limites d’une fonction qui attend « oui » ou « non »
Classes
Validité Représentants avec limites
d’équivalence
Autre chaîne
Entrées « hello word » « » « ouii »
invalides « mon »
Si les contraintes ne sont pas sous l’une des deux formes, l’on s’y ramène en
utilisant la théorie du calcul des propositions qui permet de réécrire une
formule telle que (A OU B) ET C en une forme normale disjonctive (A ET C)
OU (B et C).
Les valeurs aux limites générées dépendent de la forme de la contrainte :
Si la condition est une conjonction (ET) de M prédicats booléens :
Choisir un cas où tous les prédicats sont « justes » Vrai ; c’est la
partie « Toutes les conditions » ; dans ce cas, l’expression vaut Vrai
et la valeur attendue correspond à un calcul à partir d’entrées
valides ;
Choisir M cas avec pour chacun un seul des prédicats « juste »
Faux ; c’est la partie « Chaque condition » ; dans ce cas,
l’expression vaut Faux et la valeur attendue correspond au
traitement d’une entrée invalide.
Si la condition est une disjonction (OU) de M conditions booléennes :
Choisir un cas où tous les prédicats sont « justes » Faux ;
Choisir M cas, avec pour chacun, un seul des prédicats « juste »
Vrai.
4.3.3. Illustration
Reprenons la fonction Lendemain. Le tableau 4.11 donne, pour chaque classe
d’équivalence du problème, les valeurs limites pouvant être utilisées. Il est
clair que lorsque la classe est réduite à un élément, il n’y a guère de choix sur
les valeurs possibles.
Tab. 4.11 Valeurs limites possibles pour les classes d’équivalence de la fonction
Lendemain
Valeurs
Classe
limites Remarque
d’équivalence
possibles
Jour ∈ [1, 28] 1 et 28 Bords de l’intervalle
1 ou 10
Mois ∈ {1, 3, 5, 7, Première ou dernière valeur de
8, 10} l’énumération
Mois ∈ {4, 6, 9, Même remarque que
4 ou 6
11} précédemment
1600, 2400, Années « juste » bissextiles
Année bissextile
2000 (quatre centenaires)
Années « juste » non bissextiles
Année non 1582, 1700, (centenaires) ou limite de
bissextile 3000 l’intervalle des valeurs possibles
(1582 à 3000)
4.3.4. Commentaires
Le test aux limites améliore le test par partitionnement, mais ne le remplace
pas ; en effet, la construction des frontières, opération parfois complexe, est
généralement déduite d’une analyse préalable des partitions liées aux
domaines de définitions des paramètres.
On notera que le test aux limites permet de détecter simplement et
efficacement certains types d’erreurs de réalisation :
Mauvais opérateur de relation : X > 2 au lieu de X > = 2
Erreur de borne : X +2 = y au lieu de X +3 = y
Échange de paramètres : 2x + 3y > 4 au lieu de 3x + 2y > 4
Ajout d’un prédicat qui ferme un ensemble : (2x > 4) et (3x < 6)
Frontière manquante : (x + y > 0) ou (x + y < = 0)
Boucles mal réglées, mauvaise gestion des indices de tableau, etc.
Chaque colonne est appelée « variant » et définit un cas de test. Les valeurs
des paramètres d’entrée (le jeu de tests correspondant à ce cas de test) sont
alors choisies de telle sorte que les conditions prennent les valeurs
correspondant au variant sélectionné.
Prenons un exemple, soient trois conditions/variables C1, C2, C3 telles que :
C1 peut prendre les valeurs 0,1,2.
C2 peut prendre les valeurs 0,1.
C3 peut prendre les valeurs 0,1,2,3.
Ici, pour chaque condition C1, C2, C3, nous faisons figurer trois
informations : C.R correspond au coefficient de répétition de la condition,
c’est-à-dire le nombre de fois que chaque valeur de la condition devra être
répétée au niveau des colonnes de la table. La variable E définit l’étendue de
la condition, c’est-à-dire le nombre de valeurs possibles de la condition, et la
variable D définit le domaine de la condition, c’est-à-dire le nombre de
colonnes qu’occupent les différentes valeurs de la condition (en prenant en
compte le coefficient de répétition). Ces informations sont redondantes entre
elles, mais elles permettent de remplir ce type de table de façon très
systématique et d’exercer un contrôle visuel rapide de la cohérence de la
table. Les jeux de tests correspondant aux variants de cette table sont triviaux
à générer (chaque condition définit ici une valeur précise du paramètre).
« Nous » avons donc dix cas de tests à générer pour tester ce composant. Si
l’on veut réduire ce nombre de cas de tests, il est possible d’appliquer
l’heuristique « Toutes conditions-Chaque condition » vue précédemment.
Dans le cadre des tables de décisions, ceci revient à supprimer les variants
pour lesquels plusieurs conditions, mais pas toutes, sont vraies
simultanément. Ici, nous pourrions supprimer selon cette heuristique les
variants 7, 8, 9 et 11. En procédant brutalement de la sorte, il est possible de
ne plus voir exercer certaines actions (ici les actions A2 et A3) ce qui
reviendrait à omettre des cas de tests importants ; toutes les heuristiques
doivent être employées avec précaution et, rappelons le encore, le testeur doit
garder à l’esprit que son expertise ne pourra jamais être remplacée totalement
par une méthode ou une heuristique. En utilisant avec précaution
l’heuristique « Toutes conditions-Chaque condition » nous pouvons
néanmoins supprimer deux des trois variants 8, 9, 11 (par exemple 8 et 9) et
réduire, si cela se justifie par un gain en temps ou en ressource, le nombre de
cas de tests à 8.
Actions visibles :
A1 : changement d’année, et positionnement au 1er janvier
A2 : changement de mois et positionnement au 1er jour du mois
A3 : changement de jour et maintien du mois et de l’année
A4 : date incorrecte
Les valeurs élémentaires des paramètres influant sur le résultat sont (si l’on
ignore pour l’instant la particularité de l’année 2000 et si l’on ne considère
que les valeurs d’entrée correspondant aux contraintes des spécifications) :
Paramètre Mois :
M1 : le mois a 31 jours
M2 : le mois a 30 jours
M3 : le mois est le mois de février
M4 : le mois est le mois de décembre
Paramètre Jour :
J1 : le jour est entre 1 et 27
J2 : le jour est le 28
J3 : le jour est le 29
J4 : le jour est le 30
J5 : le jour est le 31
Paramètre Année :
Y1 : l’année est bissextile
Y2 : l’année est non bissextile
Cette table peut alors être simplifiée par l’utilisation de la stratégie don’t care
qui va permettre de regrouper un certain nombre de variants équivalents (sur
fonds grisés dans le tableau 4.14). Par exemple, lorsque le mois est dans M1,
peu importent et le type d’année et la valeur du jour tant que cette dernière
n’est pas J5 : d’où un possible regroupement des variants de 1 à 8.
Il y a quinze colonnes et donc quinze jeux de tests à générer (pour les cas
nominaux) ; à cela il faut ajouter les six jeux de tests pour les valeurs
d’entrées non conformes aux spécifications : deux valeurs de date pour des
années non correctes, deux valeurs de date pour des numéros de mois non
corrects et deux valeurs de date pour des numéros de jours incorrects.
4.4.6. Commentaires
Cette technique de test donne donc un nombre de cas de tests comparable à la
technique précédente ; ce n’est pas aberrant car lors de l’analyse et de la
définition des conditions et des actions intervenant dans une table de
décisions, nous avons été amenés à faire une analyse partitionnelle des
paramètres du composant à tester.
Pour le choix des jeux de tests, nous pouvons prendre aléatoirement une
valeur compatible avec le variant et, si nécessaire, nous pouvons réutiliser la
technique all pairs vue précédemment.
Enfin, on remarquera que cette technique donne en général de « bons » jeux
de valeurs ; par exemple, sur le cas de la fonction Lendemain, tous les cas « à
risque » sont ainsi décrits par un variant.
Pour conclure sur cette technique, nous pouvons dire qu’elle possède
d’indéniables qualités :
Elle est simple à mettre en œuvre car elle n’utilise que des tables qui
peuvent être construites avec une simple feuille de papier et un crayon
(ou encore un tableur) ;
Une table de décision complexe peut être construite par partie, en ne
prenant en compte que progressivement les différentes valeurs possibles
des conditions, puis en fusionnant les tables correspondantes (comme
cela a été fait dans l’exemple de la fonction du lendemain) ;
Les classes d’équivalences utilisées dans la technique du même nom
sont ici implicitement construites, sans effort particulier lors de la mise
en avant des actions et conditions associées du composant à tester ;
Le nombre de cas de tests, potentiellement important, peut être
facilement réduit par la mise en œuvre de la stratégie dont’ care ;
Enfin, des erreurs de spécifications peuvent être découvertes lors de la
construction des tables de décisions ce qui constitue une valeur ajoutée à
cette technique de test.
Les deux premiers objectifs ainsi que le dernier sont relativement simples à
atteindre puisqu’il « suffit » de lire le diagramme pour construire les
séquences appropriées. Pour les autres, la combinatoire peut rendre délicate
l’opération et il peut être nécessaire d’utiliser un outil adapté pour générer
automatiquement ces séquences.
Enfin, une fois les séquences construites, il faut trouver les valeurs
appropriées à passer au composant, valeurs qui permettront d’exercer la
séquence. Cette étape commence nécessairement par l’association
systématique, à chaque événement, de la façon concrète de signaler cet
événement au composant — ce qui a déjà été fait dans le cas où le diagramme
a dû être construit par le testeur du composant. Enfin, il faut que les valeurs
choisies puissent satisfaire les gardes des différentes transitions franchies ; il
n’existe a priori pas de recette « miracle » et seule une étude minutieuse des
spécifications et du diagramme états transitions permettra de trouver les
valeurs appropriées même si certains travaux récents offrent l’espoir de
pouvoir un jour générer automatiquement des données de tests à partir de tels
diagrammes.
4.5.5. Commentaires
L’utilisation de diagrammes états transitions s’avère très utile dans la
compréhension des comportements de logiciels basés sur l’interaction de
différents composants. Historiquement conçus pour décrire des protocoles de
communications (normes OSI ou RFC du monde Internet) ils trouvent un
emploi naturel dans la description des logiciels multitâches ou pour décrire
une interaction d’un logiciel avec un ou plusieurs utilisateurs. Si la
conception d’un diagramme états transitions peut être légèrement technique,
cet effort sera doublement récompensé : il fournira tout d’abord un générateur
précieux de cas d’utilisation, puisqu’il suffit de parcourir ce diagramme pour
générer une exécution possible. Deuxièmement, comme tout effort de
modélisation, il permettra de préciser certains détails absents ou ambigus
dans les spécifications et apportera ainsi des précisions utiles pour la
conception des cas de tests.
4.6. Conclusion
Les techniques de tests Boîte noire constituent l’outil principal de toute
activité de tests. Basées sur l’utilisation des spécifications du logiciel, elles
permettent de revenir à l’essentiel c’est-à-dire sur ce que doit fournir le
logiciel à ses utilisateurs. Différentes techniques sont à la disposition du
testeur, mais pour chacune d’entre elles il sera amené implicitement ou
explicitement à analyser la complétude et la cohérence des spécifications.
Cette analyse permet généralement de mettre à jour des erreurs au niveau de
ces spécifications et est donc à intégrer complètement dans l’activité du
testeur.
Chacune des méthodes présentées dans ce chapitre privilégie un point de vue.
Cependant, toutes essayent de réduire la complexité en regroupant en classes
d’équivalence des valeurs a priori distinctes mais conduisant à des
comportements similaires. Cette opération d’abstraction peut être menée sans
méthode, mais risque alors de conduire à des oublis ou à des confusions
préjudiciables à la qualité des tests. La méthode, si elle ne fait pas tout,
constitue un guide appréciable dans une activité plus complexe qu’il n’y
paraît aux premiers abords.
Dans tous les cas, une fois les cas de tests définis à l’aide d’une méthode, le
testeur gagnera à vérifier qu’aucun cas de tests important ne manque (en
particulier des cas de tests basés sur son expérience) ou que, à l’opposé,
certaines séries de tests peuvent être regroupées par un nouveau procédé
d’abstraction.
Notes
[1] Définition tirée du Grand Robert de la langue française, deuxième
édition, 2001, LTV.
[2] Voir l’article séminal : Sheldon B. Akers « Binary Decision Diagrams »,
IEEE Transactions on Computers, 27(6) pages 509–516, June 1978.
[3] Voir par exemple une des références sur le sujet : Introduction to
Automata Theory, Languages, and Computation, 3/E, John E. Hopcroft,
Rajeev Motwani, Jeffrey D. Ullman, Addison-Wesley, 2007.
[4] On pourra consulter le livre : UML 2 pour les développeurs – Cours avec
exercices corrigés, Xavier Blanc, Isabelle Mounier et Cédric Besse, Eyrolles,
2006.
Chapitre 5
L’accès aux détails de réalisation d’un logiciel permet de construire des cas
de tests complémentaires à ceux qui sont construits par les techniques dites
Boîte noire. Ces détails peuvent être le code ou sa représentation sous forme
de graphe de contrôle dans le cas des tests unitaires, un graphe d’appel entre
module dans le cas de tests d’intégration ou la connaissance de la valeur de
paramètres système (par exemple le nombre maximal de connexions ou la
valeur de temporisateurs) dans le cas de tests système.
Les tests de cette famille, appelés tests Boîte blanche ou tests Boîte de verre,
vont étudier les détails de la réalisation en compléments des spécifications
qui restent nécessaires pour, au minimum, l’analyse du résultat des tests (rôle
de l’oracle). Ces détails apportent de nouvelles informations au testeur, mais
peuvent aussi l’induire en erreur : la construction de cas de test à partir du
code risque de reproduire les mêmes erreurs dans les cas de test que celles
présentes dans le logiciel, en particulier concernant l’oubli de certaines
fonctionnalités.
C’est pour cette raison que l’on limite fréquemment l’utilisation des détails
de réalisation à la construction de mesures sur les jeux de tests utilisés,
comme les mesures de couverture ou les mesures des taux de détection
d’erreurs dans le cas d’injection volontaire de fautes.
Tab. 5.1 Table MC/DC pour la décision Freiner : = ((Vitesse > 100) or (Pente > 30)) and
((Mode = 1) or (Mode = 2))
Vitesse>100 Pente>30 Mode=1 Mode=2 Freiner Vitesse>100 Pente>30 Mode=1
1 V V V V
2 V V V F V - - 4
3 V V F V V - - -
4 V V F F F - - 2
5 V F V V
6 V F V F V 14 - 8
7 V F F V V 15 - -
8 V F F F F - - 6
9 F V V V
10 F V V F V - 14 12
11 F V F V V - 15 -
12 F V F F F - - 10
13 F F V V
14 F F V F F 6 10 -
15 F F F V F 7 11 -
16 F F F F F - - -
Il faut alors déterminer pour chaque condition les cas de tests (i.e. les lignes)
qui montrent une influence de la condition sur la décision indépendamment
des autres conditions. Pour cela on recherche des lignes « complémentaires »
vis-à-vis d’une condition, c’est-à-dire des lignes telles que le résultat de la
décision est différent alors que seule cette condition varie entre ces deux
lignes ; c’est ici le cas pour les lignes 2 et 4 vis-à-vis de la condition (Mode
= 1) : on indique alors le numéro de la ligne complémentaire dans la colonne
correspondante. On dit dans ce cas que la condition (Mode = 1) et
« couverte » par les lignes 2 et 4. En procédant de la sorte pour chaque ligne
et chaque condition on remplit la partie droite du tableau.
Une fois cette table remplie, on extrait un ensemble de lignes tel que chaque
condition soit couverte par deux lignes complémentaires de cet ensemble. On
procède par étapes et par propagation de contraintes dans le but de couvrir
toutes les conditions : par exemple, on peut partir de la ligne 6, ce qui nous
oblige à inclure la ligne 8 pour couvrir la condition (Mode = 1). Afin de
couvrir la condition (Mode = 2) il faut alors insérer la ligne 7. Avec ces trois
lignes, nous couvrons les deux conditions (Mode = 1) et (Mode = 2). Il nous
faut alors couvrir les autres conditions ; pour cela nous ajoutons, par
exemple, la ligne 14, qui nous permet de couvrir la condition (Vitesse > 100).
L’ajout de la ligne 10 nous permet alors de couvrir la condition (Pente > 30).
À ce point, la totalité des conditions sont couvertes par ces cinq lignes
(6,7,8,10,14) qui constituent nos cinq cas de tests satisfaisant le critère
MC/DC.
On remarquera que cette technique est très simple à mettre en œuvre et que
pour N conditions indépendantes, il nous faut au plus N +1 jeux de tests
différents mais qu’a contrario, il nous aura fallu construire une table de 2N
lignes pour les obtenir (ce qui limite l’utilisation de cette technique de
construction à des décisions avec assez peu de conditions). Le lecteur
intéressé pourra se référer au rapport de la NASA mentionné précédemment
pour une technique adaptée à des décisions faisant intervenir de nombreuses
conditions.
On remarquera également que cette couverture permet de détecter toute
erreur de codage portant sur une confusion sur un opérateur logique (un OR
est codé AND ou le contraire). Ainsi si la décision Freiner était codée ou
aurait dû être codée comme une des variantes F_V1 à F_V4, les jeux de tests
correspondant aux lignes 6,7,8,10,14 (en grisé dans la table 5.2) permettent
de détecter cette erreur : chacune des variantes présente au moins une
différence (en gras dans la table) quand à la valeur de la décision par rapport
à celle obtenue avec la décision initiale.
Freiner := ((Vitesse > 100) or (Pente > 30)) and ((Mode = 1) or (Mode
= 2))
F_V1 := ((Vitesse > 100) or (Pente > 30)) OR ((Mode = 1) or (Mode
= 2))
F_V2 := ((Vitesse > 100) AND (Pente > 30)) and ((Mode = 1) or
(Mode = 2))
F_V3 := ((Vitesse > 100) or (Pente > 30)) and ((Mode = 1) AND
(Mode = 2))
F_V4 := ((Vitesse>100) AND (Pente>30)) and ((Mode = 1) AND
(Mode = 2))
Considérons l’exemple proposé figure 5.5 : une fonction qui, étant donné un
tableau d’entiers T trié par ordre croissant et un entier X, devrait calculer la
plus petite valeur de T strictement supérieure à X. À droite est présenté le
graphe de contrôle associé à cette fonction. Le jeu de test (T = (0,2,4), X = 3)
permet de parcourir le chemin (B,I1,I2,I4,I2,J1,I6). Il satisfait donc le critère
de couverture « toutes les branches ». Malheureusement, deux problèmes
présents dans cette fonction ne sont pas détectés : lorsque les valeurs sont soit
toutes plus grandes que X soit toutes plus petites. Dans les deux cas, la
fonction retourne respectivement la première ou la dernière valeur, aucune
des deux ne satisfaisant le critère de recherche !
On l’a vu précédemment, le critère de couverture MC/DC permet d’améliorer
fréquemment la qualité des jeux de tests produits. Ici, pour l’obtenir, il faut
nécessairement ajouter un jeu de tests qui rende Faux la première condition
de la décision et Vrai la seconde partie, ce qui oblige à tester le cas où toutes
les valeurs de T sont plus petites que X et donc permet de détecter un des
deux problèmes de cette fonction. Cependant, ce critère de couverture
n’imposera pas de tester le cas où l’on ne rentre jamais dans la boucle ; en
effet tous ces critères basés sur les décisions, ne distinguent pas le cas où l’on
sort d’une boucle après y être passé au moins une fois du cas où l’on sort de
la boucle sans n’y être jamais entré.
Les critères de couverture « tous les i-chemins » vont imposer de passer de
zéro à i fois dans chaque boucle et permettront donc de détecter des erreurs
liées aux problèmes des boucles dans lesquelles on ne rentre pas. On n’utilise
en pratique des valeurs 1 ou 2 pour i (« 1-chemins » ou « 2-chemins ») afin
de rester dans une combinatoire raisonnable tout en améliorant
significativement le critère de couverture « toutes les branches ».
Couverture LCSAJ
Ce critère de couverture LCSAJ pour « Linear Code Sequence and Jump »
(ou PLCS pour « Portion Linéaire de Code et Saut » en français) est une n-
ième variation du critère « tous les chemins » ; sa particularité est de pouvoir
être mise en œuvre sans la construction du graphe du flot de contrôle associé
au code à tester[5]. Un chemin LCSAJ est une suite d’instructions exécutées
en séquence suivie d’un saut. On note généralement un tel chemin par trois
numéros de lignes : le numéro de la ligne du début de la séquence linéaire, le
numéro de la ligne de fin et enfin le numéro de la ligne atteinte après
l’instruction de saut. La notion de séquence linéaire doit être comprise
comme séquence de lignes contiguës (n, n +1, n +2…) parcourues lors de
l’exécution du test. Cette séquence peut donc contenir des instructions de
branchements qui ne donnent, dans cette exécution, pas lieu à un
branchement.
L’avantage de cette couverture est qu’elle est plus complète que la couverture
« toutes les décisions » sans pour autant demander la prise en compte d’un
nombre exponentiel de chemins comme avec la couverture « tous les
chemins ».
Son inconvénient, comme celui d’autres mesures basées sur le flot de
contrôle est de ne pas prendre en compte les chemins infaisables.
Fig. 5.6 Graphe de contrôle et deux possibilités de couverture « toutes les branches »
À gauche et à droite figurent des chemins d’exécution qui permettent dans les
deux cas d’obtenir le critère de couverture « toutes les branches ».
Cependant, les chemins sélectionnés sur la gauche (E B I1 I2 I3 J1 I5 I6 J2 S)
et (E B I1 I2 I4 J1 I5 I7 J2 S) ne permettent pas de découvrir l’erreur
potentielle de division par zéro en I7. A contrario, un des deux chemins
sélectionnés sur la droite (E B I1 I2 I3 J1 I5 J7 J2 S), permet de découvrir
l’erreur ou de garantir que ce cas est bien pris en compte dans le logiciel
(chemin rendu impossible par la conditionnelle I5) ; le critère de couverture
« toutes les branches » n’est donc pas pertinent vis-à-vis de ce type d’erreur.
Une fois le graphe de contrôle construit, il peut être utilisé statiquement par la
recherche de séquences particulières mettant en évidence des erreurs de
réalisation comme l’oubli de l’affectation d’une variable ou, dynamiquement
par la définition de critères de couverture liés à la vie des variables (et donc
liés à l’étiquetage des nœuds).
Figure 5.10
Integer x, y;
get(z)
for i:+0 to z loop
x:=A(i);
end loop
If x = 0 then …
Il s’agit là d’un critère assez faible. Aussi, l’on peut chercher à rapprocher les
deux précédents critères non par le bas (en diminuant les exigences) mais par
le haut. C’est ainsi que l’on peut chercher à couvrir toutes les utilisations
dans un calcul et au moins une utilisation dans un prédicat, ce qui donne le
critère « ACU + P » pour « All Computation Uses and one Predicat » (toutes
les utilisations dans un calcul et au moins dans une utilisation dans un
prédicat). Ce critère est illustré figure 5.15.
Fig. 5.15 Critère « ACU + P » (All Computation Uses + one Predicat)
Néanmoins, certains travaux récents, qui visent à construire les jeux de tests à
partir de modèles du programme ou de ses spécifications, permettent
d’envisager prochainement une automatisation efficace du traitement de ce
problème.
5.4. Conclusion
L’utilisation des détails d’implémentation permet de compléter les approches
de tests « Boîte noire » de trois façons. La première est que ces détails
rendent possible la recherche de valeur permettant d’atteindre certaines
parties du logiciel jugées importantes mais « difficiles d’accès » car
correspondant à la combinaison d’événements ou valeurs particulières plus
faciles à caractériser à l’aide du code qu’à l’aide des spécifications. La
deuxième permet de définir des critères de couverture qui donnent une
certaine garantie quant à une certaine exhaustivité des tests menés : toutes les
instructions, toutes les conditions, toutes les branches ont bien été exécutées
ou parcourues au moins par un test, ou encore toutes les utilisations d’une
variable ont bien au moins été testées une fois. Ces critères de couvertures
sont par ailleurs utilisés dans des procédures de certification des logiciels
comme à travers la norme DO-178B « Software considerations in airborne
systems and equipment certification » éditée par la compagnie américaine
RTCA et utilisée pour autoriser l’utilisation des logiciels avioniques. Enfin, la
troisième façon d’utiliser les détails de réalisation dans une activité de test est
de contrôler le respect de standards par les programmeurs au sein d’une
entreprise ou d’une communauté ou de mesurer certains aspects non
fonctionnels du logiciel, comme sa maintenabilité, sa robustesse ou sa
fiabilité.
Cependant, si ces détails offrent un regard différent au testeur et l’accès à bon
nombre d’outils permettant une automatisation poussée des tests, ils peuvent
également l’induire en erreur car ces détails définissent ce qui est fait et non
ce qui devrait être fait. Une évolution possible, qui permettrait de combiner
les avantages des tests Boîte noire et Boîte blanche, est le développement (et
l’utilisation par les programmeurs) de langages d’annotations suffisamment
haut niveau pour être le reflet quasi exact des spécifications du logiciel,
suffisamment simples d’emploi pour être utilisés sans surcoût, et
suffisamment précis pour pouvoir être manipulés de façon automatique par
des outils d’aide à la conception de jeux de tests.
Notes
[1] C’est particulièrement vrai pour la partie du logiciel qui traite le mode
nominal de fonctionnement et est à nuancer pour la couverture de la partie du
logiciel qui se charge du traitement des exceptions ou des cas singuliers.
[2] Beizer, Boris : Software Testing Techniques, Second ed., Van Nostrand
Reinhold Company, Inc., 1983.
[3] La différence provient cette fois de l’action du compilateur qui, grâce à
différents procédés d’optimisation, peut factoriser et élimer certaines parties
inutiles du code source ; le code généré, plus simple, est plus facile à couvrir.
[4] Voir par exemple le rapport de Chilenski, John J. : « An Investigation of
Three Forms of the Modified Condition Decision Coverage (MCDC)
Criterion ». FAA Tech Center Report DOT/FAA/AR-01/18, April 2001 ou le
rapport de la NASA TM-2001-210876 « A Practical Tutorial on Modified
Condition / Decision Coverage » par Kelly J. Hayhurst, Dan S. Veerhusen,
Rockwell Collins et John J. Chilenski.
[5] Voir par exemple l’article de Woodward, M. R., Hedley, D. et Hennell,
M.A., « Experience with Path Analysis and Testing of Programs », IEEE
Transactions on Software Engineering, Vol. SE-6, No. 3, pp. 278-286 , May
1980.
[6] Les tirets ne sont utilisés ici que pour améliorer la lisibilité des
expressions.
Chapitre 6
N.B. : nous avons conservé les intitulés anglais de la norme, la traduction ne posant
pas de problème.
Chacune des sphères à sa logique propre, liée aux processus et activités dont
l’autorité sera confiée soit à l’un soit à l’autre, sachant qu’une règle saine de
management stipule que l’autorité ne doit pas se partager. Le système qualité,
via le contrat, joue le rôle de médiateur entre les deux et en assure la
cohérence. C’est l’élément essentiel de la régulation globale.
Fig. 6.6 Détails des serveurs d’une application générique conforme au pattern MVC
Les fonctions métiers doivent être cohérentes avec les fonctions métiers
informatisés, via une IHM ad hoc. Les fonctions métier informatisées
peuvent être soit développées, soit acquises via des progiciels (COTS) qu’il
faudra cependant paramétrer. Idéalement, les fonctions métier informatisées
doivent être réalisées de façon indépendante de la technologie sous jacente
afin de disposer d’un degré de liberté supplémentaire si le MOA souhaite
changer de fournisseur de technologie en minimisant l’impact sur les
processus métiers proprement dits (cf. la caractéristique évolutivité de
FURPSE). Cette analyse est applicable à chacun des éléments de
l’application.
N.B. : sur le schéma, les exigences fonctionnelles et les exigences non fonctionnelles
sont clairement distinguées pour signifier que les unes et les autres sont essentielles à
l’expression de besoin et à la spécification des tests systèmes à effectuer en intégration
système et en recette.
Dans la pratique, l’intégrat aura l’aspect reproduit sur la figure 6.14. Dans le
schéma, nous avons fait apparaître :
Les données de contexte correspondant aux différents niveaux
d’intégration rencontrés couramment dans la pratique, par exemple pour
effectuer des paramétrages.
Les ressources utilisées par l’intégrat pour expliciter les règles
d’allocation et de restitution qui font partie du contrat.
La sortie non nominale correspondant à une défaillance de l’intégrat qui
va nécessiter un déroutement vers les mécanismes d’administration qui
gèrent les reprises.
Le comportement prévisible de l’intégrat lorsque le niveau de charge
augmente.
Étape E1
Chacun des serveurs est pré-intégré dans un environnement simulé qui
matérialise son contrat d’interfaces avec les autres serveurs.
Ce qui donne, pour l’IHM : le serveur « bouchon » donne des réponses
conventionnelles à partir des informations fournies par les fichiers de
données et les paramètres, de façon à pouvoir tester des scénarios
complets. Il est essentiel, pour les tests de non-régression de ce serveur
de disposer de scénarios simulés complets qui permettent d’éviter la
présence d’un opérateur humain pour les rejeux.
Étape E2
On valide la combinatoire autorisée par les mécanismes d’échanges. Les
combinaisons autorisées sont au nombre de trois, soit :
Étape E3
On valide simultanément les trois serveurs dans la logique de la recette finale.
Idéalement, les bases de données d’essais doivent être des clones des bases de
données du système réel. Les différentes configurations doivent être
conformes aux configurations rencontrées en exploitation. À ce stade de
l’intégration, certains tests peuvent/doivent être effectués sur les plates-
formes d’exploitation si le risque est considéré comme acceptable du point de
vue des usagers connectés aux systèmes. Il faut valider les installations avec
toutes les configurations de plates-formes autorisées.
En termes de coûts et de maîtrise du contrat de service (SLA) on peut
mesurer l’intérêt de rationaliser et de simplifier au maximum les plates-
formes d’exploitation, car le moindre laisser-aller se traduira par une
combinatoire infernale, et un volume de tests à effectuer incompatible avec le
budget du projet.
Fig. 6.19 Environnement d’intégration pour la recette finale
On raisonne à coûts constants. Le gain porte sur les délais, ce qui est un
avantage concurrentiel certain.
On remarquera que la conception inclut la spécification de l’environnement
d’intégration, selon le principe des architectures testables, i.e. le « design to
test » des ingénieurs hardware. Cet environnement se substitue aux pré-
intégrations. Ce faisant, l’intégration et la recette démarrent beaucoup plus tôt
dans le processus S2, avec des avances respectives Δ1 et Δ2. L’avantage
concurrentiel se matérialise par le gain en délai Δ3.
Enfin, une dernière difficulté vient des activités et processus aval. Il est rare
que les contraintes du MCO soient prises en compte complètement dès le
départ du projet, pour la simple raison que les acteurs correspondant ne sont
pas encore identifiés. Dans la mesure où c’est le processus d’intégration qui
livre les résultats du projet au MCO, les acteurs MCO et leurs contraintes
peuvent être identifiés relativement tardivement. Les plates-formes qui seront
effectivement déployées peuvent également n’être disponibles que très
tardivement.
Tout concourt donc à ce qu’un dernier train d’exigences apparaissent lors de
la transition Intégration → Qualification/Acceptation du système, et bien
souvent sous la forme de rapports d’anomalies (RA) concernant les
caractéristiques qualité non fonctionnelles. Il est par exemple très fréquent
que des problèmes de performance se manifestent sur les plates-formes
d’exploitation alors que rien n’avait été anticipé sur les plates-formes de
développement et d’intégration. La prise en compte des RA correspondants
peut s’avérer préjudiciable à l’équilibre CQFD du projet car ce type de
situation est souvent révélateur d’un grave défaut d’architecture qui est à
l’origine des mauvaises performances.
Pour pouvoir solutionner des problèmes d’architecture tardifs comme les
performances ou la maintenabilité, la conception initiale de l’architecture doit
être « ouverte » au maximum, ce qui est plus facile à dire qu’à faire ; par
exemple l’architecte peut anticiper la présence d’un cache, sans le
développer, mais en prévoyant les interfaces qui conviennent. Seul un
architecte expérimenté sera à même d’anticiper les risques et d’installer les
interfaces permettant de réagir si nécessaire. Si ce n’est pas prévu, il faudra
attendre une seconde version du système, accompagné d’une refonte
substantielle de l’architecture initiale. Toutes ces considérations sont au cœur
des réflexions de l’OMG sur l’approche MDA (Model Driven Architecture)
et le MDE (Model Driven Engineering). Pour rester dans le domaine des
performances, si la conception est suffisamment précise et rigoureuse, il est
possible de développer un modèle prédictif de performance permettant de
dimensionner les ressources indispensables pour garantir la qualité du service
rendu.
A1 et A2 sont des adaptations par des textes source que l’on peut
incorporer au programme soit avant la compilation à l’aide d’un éditeur
de texte, un pré-processeur comme dans C/C++ ou un générateur de
programme, soit pendant la compilation avec les fonctions du langage
prévue à cet effet (copy, include, etc.).
A3 est un mécanisme de l’édition de liens statique qui permet
d’incorporer au programme des objets binaires au format des unités de
compilation.
A4 est une fonction du moniteur de travaux de la plate-forme qui permet
d’incorporer au programme, lors de son lancement, un texte (ce sont des
données) qui pourra être lu par le programme à l’aide d’API système ad
hoc disponibles sur la plate-forme.
A5 est tout simplement un fichier de paramétrage que l’application
pourra consulter chaque fois que nécessaire (par exemple des
« constantes » pas assez immuables pour pouvoir être déclarées ;
3,14159… est une vraie constante, tant qu’on est en géométrie
euclidienne ; la TVA à 19,6 % est une « constante » lentement variable).
Les possibilités A1 à A5 existent depuis plus de 30 ans dans les langages
de programmation et les systèmes d’exploitation ; A6 et A7 sont
beaucoup plus récentes dans la pratique, bien que connues également
depuis plus de 30 ans, car elles nécessitent beaucoup de ressources de
performance mémoire + temps CPU.
A6 est la possibilité d’incorporer dynamiquement, au moment de
l’exécution du programme, du code et/ou des données, lors de leur
première référence. C’est le mécanisme d’édition de lien dynamique,
apparu pour la première fois avec le système MULTICS du MIT. C’est
un mécanisme d’une extrême puissance, très consommateur de
ressources plate-forme, qui permet de différer au plus tard des références
à des entités qui, faute de quoi, auraient dû être liées statiquement via
l’éditeur de liens. C’est en fait une façon de mettre à jour
dynamiquement l’application, sans avoir besoin de tout recompiler, et
surtout de tout ré-intégrer (mais il faut tout de même valider). Seul le
protocole d’appel du programme doit être standardisé, son corps viendra
au dernier moment.
Les systèmes d’exploitation disposent généralement de la propriété
d’introduire des « patchs[8] » sur un site particulier. Cette propriété très
intéressante, car réversible et locale, permet de corriger les anomalies
constatées sur une exploitation particulière sans déstabiliser tous les
sites. Le moment venu, on pourra intégrer tous les patchs à l’occasion de
la fabrication d’une nouvelle version, ou d’une révision. Les patchs sont
la matérialisation de nos imperfections et de nos erreurs ; si l’on était
dans un monde parfait, il n’y en aurait pas.
A7 est la possibilité de masquer la machine réelle hardware par une
machine purement logicielle, comme la JVM (Java Virtual Machine), ce
qui permet de disposer d’autant de types de machines que de besoin,
donc une très grande souplesse, au prix :
d’une baisse de performance très importante (facteur 100, voire
plus !), ce qui, avec les microprocesseurs actuels, ne pose plus de
problème dans certains contextes comme les IHM, et
d’une augmentation de complexité très importante, car le processus
d’IVVT déjà problématique et difficile en mode statique, est cette
fois complètement dynamique ; en conséquence, la stratégie
d’intégration est intégralement à revoir.
À chaque étape de l’intégration, les scénarios de tests de cette étape sont mis
en œuvre, ce qui permet de découvrir de nouveaux défauts. Chaque anomalie
constatée fait l’objet d’un rapport d’anomalie (RA), géré en configuration,
redirigé vers l’étape précédente pour diagnostiquer de la façon la plus précise
possible l’origine de l’anomalie et la chaîne causale du défaut, et ce jusqu’à
l’intégrat de rang 0 qu’il faudra corriger le moment venu.
Pour autant, la mécanique d’intégration n’est pas nécessairement stoppée car
des actions correctrices locales peuvent être effectuées permettant aussi
d’exécuter d’autres scénarios de tests.
Ceci nous amène à examiner les conditions d’entrée des intégrats de rang 0
dans la chaîne d’intégration. Deux attitudes sont possibles :
La situation a) est de toute évidence plus sûre et plus simple, mais elle a
l’inconvénient majeur d’aligner le processus d’intégration sur la livraison la
plus tardive. Il est très difficile de récupérer les marges de planification ainsi
offertes. D’un point de vue management de projet, ce n’est pas une stratégie
optimale.
La situation b) permet effectivement de récupérer les marges et de permettre
à l’équipe d’intégration de travailler plus efficacement. Pour ce faire, l’équipe
d’intégration peut accepter des intégrats incomplets pour autant que la
cohérence sémantique de l’assemblage soit garantie. Si ce n’est pas le cas, les
scénarios de tests sont inutilisables et il faut réaliser des scénarios adaptés.
La situation b) demande donc une coordination plus précise et plus
rigoureuse entre les équipes d’ingénierie et l’équipe d’intégration. Les
interactions résultant de ce couplage beaucoup plus étroit devront être
soigneusement gérées pour qu’elles ne transforment pas le projet en chaos
généralisé. Ceci exige un haut niveau de maturité, en particulier du côté de
l’ingénierie qui doit complètement intégrer la contrainte d’intégration dans
l’architecture et dans la programmation du système.
Le passage d’un étage d’intégration au suivant peut être détaillé comme sur la
figure 6.30.
À l’instant t, seul un sous-ensemble des entrées, des sorties et des blocs peut
être utilisé. Les blocs manquants peuvent être bouchonnés ou simulés de
façon à rendre la sortie aussi complète que possible.
En procédant ainsi, l’équipe d’intégration va pouvoir assembler un intégrat
partiel et commencer l’exécution des scénarios de tests, de façon à garantir
que les interfaces entre les intégrats fonctionnent correctement. Un scénario
de test valide une séquence particulière d’enchaînement des intégrats, d’où
l’intérêt pour l’intégration d’avoir connaissance des diagrammes de
séquences élaborés lors de l’expression de besoin et utilisés par les
concepteurs pour déterminer la solution architecturale. Si ces diagrammes ont
été modifiés, il faut bien sûr que les modifications aient été communiquées à
l’intégration.
Une séquence d’intégrats peut être schématisée comme sur la figure 6.32.
Fig. 6.32 Construction d’une séquence d’intégrats
Sur la figure 6.33, on voit qu’un certain nombre de bouchons ont été
remplacés par des blocs programmés à l’étape E2.
Cette façon de procéder permet de faciliter la séparation défaut/défaillance. Si
un nouveau défaut apparaît, il est raisonnable de penser que cela vient plutôt
des nouveaux blocs intégrés. En jouant sur la dichotomie blocs bouchonnés
— blocs intégrés, il est plus aisé d’identifier le bloc programmé qui est en
cause dans la nouvelle défaillance.
N.B. : c’est une vraie difficulté, et un risque, dans le cas de projets offshore.
Fig. 6.34 Organisation des bibliothèques de l’intégration.
Une maturité insuffisante fait courir le risque d’un chaos inextricable, car tout
devient dynamique, y compris la configuration.
L’effort de test est scindé en trois parties, selon la nature des processus
concernés par cet effort. La stratégie de test consiste à trouver l’équilibrage
de façon à optimiser la somme des coûts, et non pas les coûts individuels,
comme indiqué sur la figure 6.37.
Conformément aux schémas 6.23, 2.29 et 6.32, tout retour arrière à un coût
additionnel matérialisé par les coefficients d’amplification α. Pour optimiser
la performance des processus, une métrologie est indispensable,
conformément aux approches qualité comme celles préconisées par le SEI
avec le modèle CMM/CMMI.
En entrée en intégration, selon la statistique, il n’est pas rare d’avoir un taux
de défauts de l’ordre de 5 à 10 défauts par millier de lignes source (KLS)
nouvelles intégrées. Pour que le système soit exploitable avec un niveau de
risque contrôlé, l’intégration doit ramener ce taux à 1-2 défauts par KLS. Les
statistiques du SEI montrent que ce taux peut descendre aux alentours de 0,5,
à effort constant, pour des organisations ayant un niveau de maturité de 4 ou
5 qui maîtrisent l’intégration continue dynamique.
Fig. 6.37 Répartition de l’effort de test
Cette courbe est croissante et il est naturel de penser que l’effort pour
découvrir le défaut suivant les n défauts déjà découverts va aller en
augmentant ; l’analyse devient de plus en plus difficile, la longueur des
scénarios s’allonge, etc. d’où la concavité de la courbe.
La pente de la courbe est fonction de l’outillage et du nombre d’essais que
l’on peut effectuer. Ceci étant, pour automatiser, il faut investir en outils, ce
qui consomme de l’effort qui ne sera pas disponible pour écrire des scénarios
de test. L’équilibre est donc loin d’être évident à trouver ; tout dépend de la
taille du système, du nombre de versions installées, de la durée de vie, de la
maintenance et des évolutions effectuées.
Une représentation plus fidèle du processus d’intégration serait une courbe en
S classique, où l’on retrouverait les trois phases principales du processus,
c’est-à-dire :
Phase 1 : préparation des scénarios et de la logistique des plates-formes
nécessaire à l’intégration (environnement d’intégration).
Phase 2 : début des essais et continuation de l’effort pour produire les
scénarios d’essais.
Phase 3 : essais systématiques de tous les scénarios, avec non-
régression, et compléments éventuels avec de nouveaux scénarios.
Soit un coût moyen par défaut découvert de 6,3 heures, soit environ 1 hj
(homme-jour) si on compte les temps physiologiques et la fatigue. En
intégration, on est plutôt dans le dernier décile.
Dans des systèmes complexes de grande taille, le coût de fabrication de
scénarios d’intégration réalistes peut nécessiter un investissement de
quelques hommes mois par scénario. Le chiffrage des scénarios peut se faire
sur une base de stimuli et/ou de messages à fournir en entrée et de réponses
obtenues et validées. Si le dépouillement des résultats n’est pas automatique,
ou assisté, le coût humain peut être considérable (quelques jours ne sont pas
rares !) avec en plus un fort risque d’erreur inhérent à l’activité humaine. Le
processus d’intégration est certainement celui ou le maximum d’automatisme
doit être recherché.
En synthèse, on peut représenter les flux de la dynamique de l’intégration en
faisant ressortir la gestion de configuration qui est le processus critique du
bon déroulement des opérations, comme sur la figure 6.42.
correspondant à l’ensemble des parties que l’on peut former avec n intégrats.
Si les intégrats peuvent former des chaînes de liaisons de longueur n dans
lesquelles l’ordre d’occurrence des intégrats est significatif, i.e. il y a des
dépendances contextuelles, le nombre de chaînes à tester sera en :
N.B. : en théorie des langages, la grammaire d’un tel langage n’est pas « context-
free », ce qui rend impossible la réalisation d’un traducteur automatique. Un opérateur
doit rester dans la boucle pour lever les ambiguïtés.
Dans chacun de ces cas, les tests sont arrêtés car il apparaît que d’une façon
ou d’une autre le projet devra évoluer et qu’il faudra passer d’une phase de
tests à une phase de maintenance ou d’archivage.
7.6. Conclusion
Si l’effort de test est variable selon le type de projet et la façon dont il est
mené, il semble acquis que la proportion des coûts associés aux tests ne va
pas aller en diminuant dans le futur. En effet, la réutilisation de composants
réduit le coût global, mais paradoxalement, conduit à augmenter le taux de
tests de non-régression et d’intégration ce qui, mécaniquement, conduit à
augmenter la proportion des tests dans le projet global. Le modèle
COCOMO2 prend d’ailleurs en compte spécifiquement cette façon de
procéder dans son modèle d’estimation.
La planification des tests est un des rôles essentiels du chef de projet en
charge des tests. Celle-ci prendra d’autant plus d’importance que le projet
reposera sur de nombreux acteurs et fera appel à différents équipements
spécifiques. Il faudra prendre garde en particulier à définir des critères précis
autres que l’épuisement du temps pour décider de l’arrêt des tests.
Par ailleurs, l’évolution des modèles de développement vers des démarches
basées sur l’agilité, qui procède par itération et incrément, fait que la gestion
en configuration des tests devient un enjeu crucial. Caricaturalement, on peut
affirmer que sans gestion de configuration des tests, il ne peut y avoir
d’agilité dans le développement !
Notes
[1] Voir par exemple l’ouvrage Coûts et durée des projets informatiques :
Pratique des modèles d’estimation, Jacques Printz , Christiane Deh, Bernard
Mesdon, Nicolas Trèves, Hermes, 2001.
[2] Pour un projet de type Simple, type S, k vaut 2,4. Pour un projet de type
semi détaché, type P, k vaut 3,0 et enfin pour un projet Embarqué, de type E,
k vaut 3,6.
[3] La loi de Parkinson fut exprimée en 1957 par Cyril Northcote Parkinson
dans son livre Les Lois de Parkinson (C. Northcote Parkinson, Parkinson's
Law, or The Pursuit of Progress, John Murray, London, 1957) et est basée sur
une longue expérience dans l’administration britannique et stipule que « le
travail s’étale de façon à occuper le temps disponible pour son achèvement ».
On trouve assez facilement cet ouvrage sous forme numérique.
Chapitre 8
Comme pour toute activité humaine, l’utilisation d’un outil dans le cadre du
test d’un logiciel permettra de gagner en qualité et en efficacité (les tests
seront meilleurs et permettront de détecter plus de défauts) mais permettra
aussi de réaliser des gains en efficience (les tests seront menés avec la même
efficacité mais avec moins de ressources et en moins de temps).
Par ailleurs, on notera que si pour certains types de tests l’utilisation d’un
outil est une option elle devient une quasi-obligation pour toute une frange
des tests. C’est le cas d’une grande part des tests dynamiques : tests de
charge, de performances, de stress ou de robustesse ; c’est aussi le cas pour la
plupart des tests structurels qui sont basés sur une analyse automatique des
codes des programmes. C’est aussi le cas si l’on veut réellement mettre en
œuvre des tests de non-régression dont l’intérêt est de pouvoir être refait très
facilement dès que l’on modifie le logiciel pour corriger un défaut ou pour
ajouter une fonctionnalité.
Cependant, comme pour l’utilisation de tout outil, la transformation d’une
activité manuelle en une activité partiellement ou complètement automatisée
à l’aide d’un outil qui a sa propre logique et ses propres contraintes va induire
des transformations plus profondes au sein de l’entreprise que la simple
introduction de l’outil, transformations, positives ou négatives qu’il ne faudra
pas sous-estimer.
L’objectif de ce court chapitre est de faire le point sur ce domaine en
l’abordant sous ses différentes facettes.
S’il est donc clair qu’un outil adapté et bien utilisé apporte de nombreux
bénéfices à la réalisation des différentes activités liées aux tests, il ne faut pas
sous-estimer les risques inhérents à l’introduction d’un outil dans une
organisation. Parmi les plus importants, nous pouvons citer :
La sous-estimation du temps nécessaire à la mise en place de l’outil et à
son utilisation de manière efficace ; il s’agit là d’un défaut d’optimisme
fréquent, encouragé par ailleurs par les ingénieurs d’affaires ou autres
commerciaux, qui peut conduire à un véritable désastre en termes de
mobilisation des équipes techniques et des services métiers.
Le mauvais choix d’outil ; bien que cela semble une erreur grossière et
simple à éviter, il peut arriver que du fait d’inadéquations techniques de
besoins mal estimés, ou en encore de décalage en terme de connaissance
nécessaires et maîtrisées, le choix se porte sur un outil inutile pour
l’entreprise ou très difficile à utiliser.
Une perte en qualité (valeurs générées, processus en place) ou une
désorganisation profonde des équipes ; en effet, la plupart des outils sont
conçus pour répondre aux besoins du plus grand nombre et supposent un
modèle d’organisation donné. Dans le cas où l’entreprise, du fait de son
histoire, de son savoir-faire ou de son marché, a mis en place un modèle
de fonctionnement spécifique, l’outil choisi peut se révéler être
inadaptable à ce modèle ; l’utilisation de l’outil risque alors de se heurter
au mode de fonctionnement en place et créer des tensions et des erreurs
qui vont faire régresser la qualité des tests.
Enfin, pour la plupart des besoins, l’on pourra se tourner vers une solution
« propriétaire » ou vers une solution « Open Source ». Il n’est pas nécessaire
de rappeler qu’ouvert ou libre ne veut pas dire gratuit et que payer cher une
solution garantit une adéquation aux besoins.
Tests unitaires
Les tests unitaires se sont considérablement développés avec l’apparition des
outils xUnit, ou plus précisément des cadres de développements
(frameworks) xUnit : jUnit (pour le langage Java), jsUnit (pour le langage
JavaScript), cUnit (pour le C), cppUnit (pour le C++), phpUnit (pour le PHP),
etc. la lettre x faisant référence au langage ciblé. Ces structures logicielles
sont des évolutions de SUnit mis au point par Kent Beck en 1994 pour le
langage objet avant-gardiste Smalltalk et fournissent un canevas pour la
production automatisée de tests unitaires, principalement des méthodes dans
le cadre des langages objets. Cette automatisation poussée est la clef du
succès rencontré par ces technologies puisque toute l’architecture des tests est
fournie ou produite automatiquement.
Ainsi, avec un environnement de développement récent (Eclipse par
exemple) l’effort de test se résume à l’essentiel, i.e. choix des bons scénarios
et des bonnes valeurs pour mener les tests et construction de l’oracle pour ces
valeurs de tests à l’aide « d’assertions » qui seront utilisées dans les « tests
suites ». Ces assertions permettent de définir que l’on attend une valeur
donnée dans l’évaluation d’une expression ou qu’une exception doit être
levée dans les conditions de tests choisies. L’exemple suivant (figure 8.1)
illustre les différentes possibilités offertes par la version 4 de l’infrastructure
jUnit[4] (exemples tirés de la documentation associée à cette infrastructure) ;
on notera l’utilisation d’annotations (@Test) qui permettent de donner de
directives ou recommandations au compilateur ce qui simplifie encore la
conception des tests unitaires par rapport aux versions qui utilisent
uniquement les concepts objets (comme jUnit3). La première méthode permet
de tester qu’à la construction, un tableau dynamique est vide. Dans le second
test on utilise une annotation qui spécifie que l’exception de la classe
IndexOutOfBoundsException doit être levée durant l’exécution de la
méthode qui suit (méthode outOfBounds). Si l’exception n’est pas levée ou si
une autre exception est levée durant ce test, le test échoue. La troisième
possibilité illustrée dans cet exemple concerne la durée maximale d’exécution
d’une méthode. Au-delà de cette durée (dont la valeur en millisecondes est
passée en paramètre) la méthode est arrêtée et le test échoue.
public class Example {
@Test
public void method() {
org.junit.Assert.assertTrue( new ArrayList().isEmpty() );
}
}
@Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
new ArrayList<Object>().get(1);
}
@Test(timeout=100) public void infinity() {
while(true);
}
Cet exemple montre bien en quoi une infrastructure comme jUnit simplifie la
construction des tests unitaires. Couplée à un environnement de
développement comme Ecplise[5], cette infrastructure permet de plus de jouer
et rejouer les tests très facilement à l’aide de boîtes de dialogues qui
permettent de personnaliser l’environnement d’exécution des tests (par
exemple en initialisant un environnement spécifique d’exécution avant de
démarrer les tests). Les résultats d’exécutions des tests sont alors présentés
par une barre latérale qui permet de suivre la progression des tests comme
illustré sur la figure 8.2.
Fig. 8.2 Exemple d’utilisation de jUnit sous Eclipse
Une fois les scénarios de tests définis, on les exécute puis l’outil recueille les
résultats qu’il présente soit sous forme de table, soit sous une forme
graphique comme présenté sur la figure 8.4.
8.3. Conclusion
Un outil, bien choisi, permet sans conteste un gain dans la qualité et
l’efficacité du travail réalisé. Le domaine des tests est doté d’une grande
panoplie d’outils couvrant l’ensemble des activités de tests. Cependant avant
le choix et l’installation d’un nouvel outil il est important d’auditer les
processus et les pratiques existantes afin d’identifier les apports potentiels
d’un outil en terme d’amélioration et/ou d’optimisation des pratiques en
place.
Une fois les axes d’amélioration identifiés, il faut faire l’évaluation des
produits du marché en incluant des critères tels que la qualité de la formation
associée, la qualité du support technique, la communauté d’utilisateurs, les
retours d’expérience que l’on peut recueillir. L’évaluation des différents
logiciels disponibles se fera après avoir défini des critères objectifs et clairs
qui permettront l’évaluation des différents produits par rapport aux
améliorations et/ou optimisations visées. Assister à des démonstrations
d’outils pour mieux appréhender les apports et les difficultés prévisibles et
bien entendu efficaces dans cette phase de réflexion.
Une fois une liste restreinte de solutions retenues (dans l’optimal pas plus de
trois candidats) il va être intéressant de mettre en place un pilote sur un ou
deux projets afin d’avoir un retour concret de la solution envisagée dans le
contexte de l’entreprise. Une revue détaillée de ces quelques
expérimentations menées permettra de choisir de façon éclairée. Sans que le
choix donne lieu à une décision collective, il est important de mettre dans la
boucle tous les acteurs potentiellement impliqués dans l’usage au quotidien
de cet outil. Une oreille attentive pourra y trouver une confirmation que les
bénéfices attendus seront bien au rendez-vous et que les risques encourus ne
risquent pas de rendre caduc cet investissement.
Une fois l’outil retenu et installé puis configuré, il faut mettre en place une
stratégie d’accompagnement des différents collaborateurs impliqués. Cela
inclut de la formation à l’outil mais aussi la mise en place d’une cellule
d’appui aux utilisateurs qui saura conseiller et guider ces utilisateurs dans la
transformation de leur façon de procéder. À noter que cet accompagnement
est nécessaire même si l’outil semble accessible et convivial.
Enfin pour conclure ce chapitre, il ne faut pas oublier qu’automatiser une
partie des tests est un projet en soi avec des développements et qu’en partie
du fait des tests de non-régression, la durée de vie des tests peut être grande
et nécessite de les gérer dans le temps comme tout autre projet.
Notes
[1] Parmi ces règles on peut citer la structuration du programme en sous-
programmes ou modules ou classes, la déclaration des variables et des sous-
programmes, le typage fort, etc.).
[2] Les liens vers les logiciels cités, ainsi qu’une rapide description de ces
logiciels sont disponibles sur www.dunod.com/contenus-
complementaires/9782100706082.
[3] On pourra lire à cet effet le blog John Carmack, développeur de Doom et
Quakede, fondateur de la société Armadillo Aerospace, à l’url
http://www.altdevblogaday.com/2011/12/24/static-code-analysis/.
[4] http://junit.org
[5] http://www.eclipse.org
Chapitre 9
Tester revient à confronter par des moyens statiques (analyse de code, revue,
etc.) ou par des moyens dynamiques (exécution avec des valeurs
particulières) les spécifications du logiciel, c’est-à-dire ce qu’il doit faire et
éventuellement sous quelles contraintes (temps, utilisation de la mémoire,
etc.) à sa réalisation, c’est-à-dire de quelle façon il répond au besoin exprimé
en enchaînant différentes actions élémentaires.
Trouver des jeux de tests pertinents, c’est-à-dire permettant de trouver des
défauts dans la réalisation est, comme nous l’avons déjà dit, une activité qui
va donc viser à déterminer, à partir des documents de spécifications, quelles
valeurs intéressantes peuvent permettre de confronter la réalisation à celle
attendue dans l’absolu (la réalisation qui implémente exactement les
spécifications, spécifications qui sont supposées être sans défauts).
Générer automatiquement des cas de tests nécessite donc une analyse
automatique des spécifications pour déterminer quelles valeurs seront
pertinentes et lesquelles ne le seront pas. Les spécifications étant, dans la
majorité des cas, rédigées en langues naturelles, il est dans l’état actuel des
connaissances impossible d’automatiser de façon efficace cette activité sans
apports d’informations complémentaires. Nous omettons volontairement les
cas où le code est généré automatiquement à partir de spécifications
complètement exprimées à l’aide de langages formels (par exemple, dans le
cadre de l’utilisation de la méthode B (Abrial, 1996[1]).
Nous détaillons ici quelques pistes et travaux récents qui apportent une
réponse partielle à cette problématique et qui dans certains cas ont donné (et
donneront de plus en plus) de véritables résultats en termes d’amélioration de
la qualité des logiciels produits ainsi qu’en termes de réductions des coûts
engendrés par les tests. On trouvera une étude assez complète faite en 2013
dans Saswat Anand et al.[2] Chacune de ces stratégies va s’appuyer sur des
compléments d’informations provenant par exemple de :
TGV
TGV (Test Generation from transitions systems using Verification
techniques) est un outil qui fait partie de la suite d’outils CADP
(Construction and Analysis of Distributed Processes) développée par
l’INRIA depuis les années 90 et vise à fournir une solution à la génération de
suites de tests pour des protocoles ou des systèmes asynchrones embarqués
ou répartis. Cet outil se base sur une description du système à l’aide d’une
forme de système de transitions étiquetées[7].
Smartesting CertifyIt
L’outil CertifyIt[8] de la société française Smartesting[9] permet de générer
des suites de tests avec différents objectifs de couverture (couverture des
exigences, des transitions, etc.) à partir de différents types de modèles comme
les diagrammes d’états UML (Statecharts), des scenarios de comportement
décrits avec le langage BPMN ou encore des pre et post conditions sur les
actions modélisées à l’aide du langage OCL (Object Constraint Language).
L’outil offre aussi de nombreuses possibilités de suivi et de gestion des
campagnes de tests.
Conformiq Designer
Conformiq Designer est un outil permettant la génération automatique de
suites de tests à partir de diagrammes UML états-transitions et/ou de
description de comportement attendu à l’aide du langage Java. Les suites de
tests générées peuvent également être exportées dans différents langages
incluant le langage TTCN-3 et analysées à l’aide de diagrammes de
séquences de messages (Message Sequence Chart). Cet outil est
commercialisé par la société Conformiq[10] et est distribué en France par la
société VerifySoft[11].
PathCrawler
PathCrawler[15] est un outil ancien et robuste faisant maintenant partie de la
plateforme Frama-C et permet de générer des jeux de tests pour des
programmes écrits en langage C. Développé et maintenu conjointement par le
CEA List et l’INRIA Saclay, cet outil est disponible sous licence GPL ou
sous licence commerciale[16] ; une utilisation en ligne est également
disponible permettant de mesurer l’intérêt (ou pas) d’utiliser ce logiciel dans
un cas donné.
JavaPathFinder (JPF-SE)
JavaPathFinder est un outil développé par la NASA depuis de nombreuses
années dans le but de vérifier des programmes Java à l’aide de différentes
stratégies de vérification de modèles (Model Checking). Il a été étendu en
2007[17] par l’ajout d’un module d’exécution symbolique permettant ainsi de
générer des jeux de tests sur des programmes Java, incluant les programmes
multitâches. Cet outil est disponible gratuitement et peut être obtenu à partir
du site babelfish de la NASA[18] et peut être intégré dans les environnements
de développement Eclipse[19] ou NetBeans[20].
9.3. Conclusion
La génération de jeux de test par des procédures automatiques est une volonté
ancienne des équipes projets. Longtemps inaccessible du fait de théories trop
complexes et difficiles à mettre en œuvre avec des moyens techniques
limités, cette approche est maintenant rendue possible par d’un côté l’accès à
des puissances de calculs et de mémoire pour un faible coût et par une grande
maturité des approches, en particulier l’approche basée sur les modèles
(approche MBT) qui apporte une véritable amélioration de la qualité des
logiciels produits moyennant une appropriation pas toujours évidente de
l’aspect modélisation.
Notes
[1] Abrial, J., Hoare, A., & Chapron, P. (1996). The B-Book: Assigning
Programs to Meanings. Cambridge: Cambridge University Press.
doi:10.1017/CBO9780511624162
[2] Saswat Anand, Edmund K. Burke, Tsong Yueh Chen, John Clark, Myra
B. Cohen, Wolfgang Grieskamp, Mark Harman, Mary Jean Harrold, and Phil
Mcminn. 2013. An orchestrated survey of methodologies for automated
software test case generation. J. Syst. Softw. 86, 8 (August 2013), 1978-2001.
DOI=http://dx.doi.org/10.1016/j.jss.2013.02.061
[3] P. Dauchy, M.-C. Gaudel, and B. Marre. 1993. Using algebraic
specifications in software testing: a case study on the software of an
automatic subway. J. Syst. Softw. 21, 3 (June 1993), 229-244.
DOI=http://dx.doi.org/10.1016/0164-1212(93)90025-S
[4] T. S. Chow. 1978. Testing Software Design Modeled by Finite-State
Machines. IEEE Trans. Softw. Eng. 4, 3 (May 1978), 178-187.
DOI=http://dx.doi.org/10.1109/TSE.1978.231496
[5] Chen, Z., Xu, B., Yang, R., & Zhang, Z. (2015). EFSM-Based Test Case
Generation: Sequence, Data, and Oracle. International Journal of Software
Engineering and Knowledge Engineering, 25, 633-668
[6] Jan Tretmans. 2008. Model based testing with labelled transition
systems. In Formal methods and testing, Robert M. Hierons, Jonathan P.
Bowen, and Mark Harman (Eds.). Lecture Notes In Computer Science, Vol.
4949. Springer-Verlag, Berlin, Heidelberg 1-38.
[7] http://cadp.inria.fr/, TGV
[8] http://www.smartesting.com/fr/certifyit/
[9] http://www.smartesting.com/
[10] https://www.conformiq.com/
[11] http://www.verifysoft.com
[12] https://msdn.microsoft.com
[13] Jabier Martinez, Tewfik Ziadi, Tegawendé F. Bissyandé, Jacques Klein,
and Yves Le Traon. 2015. Bottom-up adoption of software product lines: a
generic and extensible approach. In Proceedings of the 19th International
Conference on Software Product Line (SPLC '15). ACM, New York, NY,
USA, 101-110. DOI=http://dx.doi.org/10.1145/2791060.2791086
[14] http://dep-mathsinfo.parisnanterre.fr/
[15] http://frama-c.com/pathcrawler.html
[16] http://frama-c.com/download.html/
[17] Saswat Anand, Corina S. Păsăreanu, and Willem Visser. 2007. JPF-SE:
a symbolic execution extension to Java PathFinder. In Proceedings of the
13th international conference on Tools and algorithms for the construction
and analysis of systems (TACAS'07), Orna Grumberg and Michael Huth
(Eds.). Springer-Verlag, Berlin, Heidelberg, 134-138.
[18] https://babelfish.arc.nasa.gov/trac/jpf/wiki/install/start
[19] http://www.eclipse.org/
[20] https://netbeans.org/
Chapitre 10
Les systèmes embarqués et les services Web sont utilisés dans des contextes
différents mais présentent, du point de vue des tests, des caractéristiques
communes : ils interagissent avec de nombreux systèmes extérieurs (variés et
ayant une grande variabilité), ils sont généralement composés de sous-
systèmes qui collaborent pour fournir les services pour lesquels ils ont été
conçus, les caractéristiques testées dépassent généralement le cadre
fonctionnel et incluent les problèmes de charge, de fiabilité et de sûreté. Plus
généralement, on peut aussi constater que c’est au niveau des interactions que
les défauts vont fréquemment apparaître et que les problèmes de temps de
réponse sont cruciaux dans ces deux univers.
Nous détaillons maintenant quelques aspects saillants des stratégies de test à
mettre en œuvre dans ces deux domaines.
Comme pour les systèmes embarqués, les services Web sont donc des
applications relativement autonomes qui interagissent avec un monde qui
évolue en permanence. Elles vont donc pouvoir être testées par des approches
similaires, à la fois sur les aspects fonctionnels mais aussi sur les aspects
performance et robustesse.
La spécificité est ici que les services Web sont testés principalement sous la
forme de tests d’intégration sans faire appel à des notions de couverture de
code qui s’applique mal dans ce contexte.
Pour ce faire on décrira des scénarios que l’on exécutera de façon
automatique avec des outils comme :
10.3. Conclusion
Nous avons vu dans ce court chapitre que le test des systèmes embarqués et
des services Web ont de nombreux points communs en particulier dans
l’approche méthodologique des tests : prendre grand soin de
l’environnement, se focaliser sur les interactions, les notions de sûreté et de
robustesse face à la charge et aux fautes. Les différences existent néanmoins
puisque les aspects de contraintes techniques pèsent peu sur les services Web
alors qu’elles sont prégnantes sur les systèmes embarqués.
On constate actuellement également une grande convergence de ces deux
mondes, en particulier dans le domaine de l’automobile où les systèmes
embarqués vont de plus en plus communiquer avec des services Web, à la
fois sur des questions de confort ou de guidage mais aussi sur des questions
de sécurité dans les sous-systèmes d’aide à la conduite ou de gestion
d’urgences.
Cette convergence va sans doute impliquer des changements dans la façon de
concevoir les systèmes embarqués et donc dans la façon de les tester avec la
mise en place de stratégies globales et d’outils avec une couverture
fonctionnelle plus large.
Notes
[1] http://www.esterel-technologies.com/
[2] http://www.college-de-france.fr/site/gerard-berry/course-2013-2014.htm
[3] https://fr.mathworks.com/
[4] “IEEE Standard Glossary of Software Engineering Terminology,” IEEE
Std 61012-1990, pp. 1–84, Dec. 1990
[5] S. M. A. Shah, D. Sundmark, B. Lindström, and S. F. Andler,
‘Robustness Testing of Embedded Software Systems : An Industrial
Interview Study’, IEEE Access, vol. 4, pp. 1859–1871, 2016
[6] https://www.ietf.org/rfc/rfc2396.txt
[7] Apache JMeter, http://jmeter.apache.org/
[8] https://www.soapui.org/
[9] https://smartbear.com/
[10] http://www.testing-whiz.com/web-services-testing
Chapitre 11
11.1. Introduction
Dans ce chapitre un peu prospectif nous voulons élever le débat et regarder
les tests comme une activité fondamentale de l’ingénierie des systèmes qui va
structurer le futur de nos sociétés hyper connectées, alors que les prévisions
font état de 50 milliards d’objets interconnectés via Internet vers 2030. Dans
les projets informatiques, la tendance vers de plus en plus d’intégration est
irréversible. Les projets dits d’intégration sont de plus en plus nombreux, et
dans ces projets l’activité IVVT est prépondérante. Le travail consiste alors à
élaborer des scénarios de tests, avec les données qui conviennent,
éventuellement obtenues par simulation. Dans ces projets, on comprend tout
de suite que la complexité ne réside évidemment pas dans les lignes de code
constitutives des modules que l’on intègre mais dans les interactions entre ces
modules compte tenu des nouveaux usages. Ce sont ces interactions qu’il faut
valider, et c’est pour cela que les scénarios de tests et l’effort consacré à leur
mise en œuvre sont un indicateur de complexité pertinent du point de vue du
chef de projet. D’où la définition que nous proposons :
Notons que cette définition prend en compte les coûts d’ingénierie des
modules constitutifs du système qui entrent dans le processus d’intégration
décrit précédemment, lesquels peuvent être appréhendés via un modèle
d’estimation type COCOMO ou Points de fonctions. L’accent est mis ici sur
les interactions que les protocoles d’interfaces autorisent.
Intégrer l’information dans l’entreprise numérique, c’est mettre en cohérence
trois ensembles de trajectoires évolutives, bien distinctes mais étroitement
corrélées :
Plus d’interactivité, plus d’agilité = Découpage plus fin des fonctions = Plus
d’éléments en interactions = intégration plus difficile, donc plus coûteuse.
11.2. Terminologie
Nous utilisons le terme « numérique » pour caractériser l’état présent et futur
des systèmes informatisés qui gouvernent le fonctionnement de la société et
de ses grands équipements (énergie, télécommunications, transport, défense
et sécurité...), tant au niveau des entreprises et des administrations, que des
particuliers, où l’ordinateur est devenu un artefact quasi microscopique, « qui
ne se voit plus », mais omniprésent, dont on ne peut plus se passer. Le
meilleur indice de cette « numérisation » massive est la quantité de logiciel
que chacun de nous utilise quotidiennement comme par exemple utiliser son
smartphone, rechercher un itinéraire avec Google Maps ou son GPS, ou
simplement allumer la lumière, ou encore donner un bon coup de frein qui
active le dispositif ABS de sa voiture. Rappelons tout de même que le terme
« informatique » forgé par la fusion de information et automatique dans les
années 60, garde tout son sens, car il s’agit bien d’automatiser le traitement
de l’information. Sans ces automatismes, il n’y aurait ni Cloud Computing, ni
Autonomic Computing, ni Big Data, ni téléphone mobile, … car les flux
associés dépassent désormais de très loin les capacités humaines.
Au sortir du dernier conflit mondial, cet indice logiciel était strictement égal à
zéro ! Aujourd’hui, derrière nos gestes les plus élémentaires se cachent des
dizaines de millions de lignes de programmes, fruit du travail et de
l’intelligence de millions de programmeurs, logiciels qu’il faut concevoir,
développer, valider, entretenir, et retirer proprement du service lorsqu’ils
deviennent obsolètes, car on ne les trouve pas à l’état naturel comme les
matières premières. Les ordinateurs et les équipements informatisés nomades
qui permettent l’interaction en temps réel de millions d’usagers vont mettre
en exécution ces logiciels pour rendre à l’usager le service attendu, avec le
niveau de qualité requis par ces usagers qui ignorent tout de la complexité
sous-jacente. La puissance de traitement et de stockage de l’information est
fournie par des infrastructures regroupant des centaines de milliers de
serveurs qui rendent accessibles à tous le Software as a Service, [SaaS], un
peu comme l’électricité ou le téléphone, par un simple branchement à cette
nouvelle source d’énergie. Simplicité des usages, complexité des
infrastructures et des plates-formes, complexité de la programmation des
équipements et des interfaces, tel est le dilemme de l’intégration pour
l’entreprise numérique.
Dans le corps du texte nous serons amenés à parler de taille de programmes,
exprimée depuis les années 70-80 en nombre d’instructions source réellement
écrites par les programmeurs en COBOL, C, C++, Java, Python, ... [longue
liste], à l’exclusion de celles fabriquées par des outils, conformément aux
règles de comptage standardisées en vigueur[1] ; Ainsi 1.000 lignes de scripts
peuvent activer un progiciel de grande taille, mais seules les 1.000 lignes ont
été écrites. Pour rendre concrète cette notion de ligne source [LS, ou KLS
pour 1.000 LS] il est commode de les représenter au format imprimeur, c’est-
à-dire en nombre de pages (en moyenne, dans l’édition courante, 50 lignes de
texte par page), soit :
Le coût pour 1.000 lignes de code, c'est-à-dire 1 KLS dans le modèle
COCOMO correspondrait à 2 ou 3 hommes_mois de développement.
Une erreur dans un tel texte, c’est une erreur sur 1.000 LS, ce qui est
déjà considéré comme une bonne performance : c’est une unité de
compréhension qui respecte les règles ergonomiques humaines, en
particulier la capacité de mémorisation du programmeur, indispensable à
la qualité de son travail.
Pour 10.000 LS, c’est un livre moyen de 200 pages.
Pour 100.000 LS, c’est 2.000 pages, soit cinq « gros » livres de 400
pages, une taille d’ouvrage considérée comme une limite ergonomique,
à la fois pour l’auteur et pour le lecteur ; difficile à mémoriser, y
compris pour l’auteur qui doit avoir « en tête » tout le contexte. C’est
typiquement le fruit du travail d’une équipe projet agile.
Il est bon d’avoir ces chiffres à l’esprit pour comprendre la nature du travail
des programmeurs qui par certains cotés est analogue à un travail d’écrivain
et de traducteur, voire de mathématicien, mais surtout celui d’un créateur et
architecte de modèles.
Mais pour être complet, il faut ajouter à cette bibliothèque, le mode d’emploi,
c’est-à-dire. les référentiels système, les aides en lignes [souvent intégrées au
texte même du programme], et surtout les scénarios de tests qui permettent de
déclarer la bibliothèque « bonne pour le service » aux usagers. Ce qui
reviendrait à doubler sa taille !
C’est une première cartographie, certes pas très utile, mais qui cependant
montre bien la nature des liens que cette cartographie va entretenir avec son
environnement. Là où les choses deviennent vraiment intéressantes est
lorsqu’il va s’agir de structurer cette « masse » par rapport aux organisations
qui l’utilisent et qui la gèrent (cf. figure 11.2).
Fig. 11.2 Interaction des organisations – Ecosystème
NB : En programmation objet, avec des langages comme Java, C#, Python, ...
c’est une classe de 8-10 méthodes, chacune comptant en moyenne 100-125
LS, soit deux pages de texte, c’est-à-dire une entité qui a du sens pour le
programmeur.
Quelques données ergonomiques qui structurent l’activité des
programmeurs :
Une lecture attentive de 20 pages de programme, c’est 2-3 heures de
concentration intense, sans distraction ni interruption.
On peut estimer qu’une reprise de ce code par le programmeur qui l’a
conçu et développé, après un an, c’est environ 10% de l’effort initial,
soit cinq jours de travail. Un bon programmeur gardera en mémoire la
structure générale du programme, mais oubliera les détails qu’il
reconstruira cependant sans difficulté.
Le même travail de reprise, par un tiers, sans connaissance préalable,
c’est 20-30%, soit 2 à 3 semaines d’appropriation, si la documentation
est bien faite, pour effectuer des modifications.
Une productivité de 20.000 LS par an est une performance atteinte par
moins de 10% des programmeurs [c’est 5 fois la moyenne statistique de
4.000 LS à ±10%]. Si le programmeur qui annonce de tels taux n’est pas
classé comme excellent, il est probable que plus de la moitié du travail a
été oublié ou omis : programmation peu rigoureuse sans respect des
règles de bonnes pratiques, absence de documentation, tests incomplets,
etc. ... d’où la notion d’âge « moyen » en Compétence/Expérience de
l’organisation de l’engineering [sous-traitants inclus] qui est une
moyenne pondérée, c’est-à-dire un barycentre, de l’expérience de
chacun.
Vous noterez que sur l’arborescence, nous n’avons pas fait apparaître une
typologie actuellement en vogue, celle des « services », pour des raisons de
simplification. Un service, dans la mesure où il est partagé est astreint à des
règles strictes d’évolution/adaptation de même nature que celles des données
partagées entre plusieurs programmes nécessitant un service d’accès.
Dans tous les cas, c’est à l’architecte de définir ce qui est important pour le
cycle de vie du système et de le faire figurer comme tel dans l’arborescence
des entités constitutives du système. Ces entités, avec leurs attributs, sont des
types et doivent être rigoureusement documentées dans le référentiel du
système.
Cet inventaire ayant été fait, on peut dresser la liste des modules externes qui
sont en interactions directes [couplages], avec l’un quelconque des modules
appartenant à l’inventaire.
La gestion de configuration système est indispensable à ce stade.
L’ordre dans lequel les modules vont pouvoir être intégrés dépend de la
qualité du travail architectural. En effet, comme il a été dit ci-dessus, si c’est
le laisser-faire qui prédomine, il faut s’attendre à ce que tous les modules
dépendent les uns des autres, ce qui va se traduire par la saturation de la
matrice de couplage. Au contraire, si l’architecte a mis en œuvre les bonnes
pratiques architecturales : architecture modulaire hiérarchique, architecture en
couches, architecture des interfaces, les relations entre modules vont pouvoir
être traduites dans la structure centrale qui pilote l’intégration : l’arbre
d’intégration (cf. réf. [4]). Cet arbre d’intégration est l’un des résultats
fondamentaux du processus de conception de l’architecture. Pour
l’intégration, la situation se présente comme suit [figure 11.8].
Fig. 11.8 L’arbre d’intégration
La somme des tests à effectuer sur chacun des nœuds de l’arbre, en partant
des tests de pré-intégration est une bonne mesure de la complexité de
l’intégration, et la somme des coûts, à droite sur la figure 11.8, traduit le coût
de l’organisation de cette complexité.
L’un des principaux résultats qui émerge des études sur les tests, au sens
IVVT, a été en effet de valider que toute la remontée du cycle de
développement dans sa formulation en V doit, mais surtout peut être
anticipée dès la phase de conception du système. Après, il est trop tard, ou en
tout cas beaucoup plus coûteux, pour faire les réajustements indispensables.
La qualité du travail architectural doit donner suffisamment d’information
pour mettre en œuvre des modèles d’estimation comme ceux que nous avons
présentés dans nos ouvrages récents (cf [2] et [4]). Du point de vue de la
complexité et des coûts, on peut représenter la situation comme suit (figure
11.9).
Fig. 11.9 Complexité complication de l’arbre d’intégration
Des lignes de code isolées sont plus « froides » que des lignes de code intégrées, c'est-
à-dire moins coûteuses. Dans le cyberespace, par défaut, tout est intégré, tout est lié,
tout est plus « chaud », ce qui maximise la potentialité de découvrir de nouveaux
usages.
Tant pour l’organisation des processus métiers que pour l’agencement des
processus industriels l’architecture est le point de passage obligé d’une
ingénierie proactive, prédictive et durable, qui sait prévoir ce qui va se passer
et en mesurer l’avancement.
Il faut cesser de penser que l’architecture est un centre de coûts. Les bonnes pratiques
qualité comme la mise en œuvre intelligente du CMMI montrent à l’évidence que tout
investissement effectué en amont sera largement compensé par des gains en aval d’un
facteur 10 à 50 pour l’intégration, beaucoup plus (> à 100) après la mise en
production.
Chacun doit se convaincre qu’il est désormais plus simple, moins coûteux,
plus sûr de raisonner sur des modèles partagés entre tous les acteurs et parties
prenantes que sur le système lui-même. L’arbre d’intégration qui en est le
résultat est l’instrument concret, pragmatique, qui permet de synchroniser et
de contrôler les trajectoires des différents projets qui contribuent à la
réalisation du système conforme aux besoins de ses usagers réels.
Les textes associés à ces modèles sont la matière première de la mesure de
complexité fondée sur l’activité IVVT.
Les bonnes pratiques de modélisation et leur mise en œuvre par chacun des acteurs
concernés est le sésame qui permet à l’entreprise numérique d’exploiter son bien le
plus précieux : l’INFORMATION, au service de ses usagers, et d’en apprécier la
valeur au sens économique du terme.
La conception de logiciels reste avant tout une activité humaine ; aussi, n’est-
elle pas exempte d’erreurs. Des erreurs seront commises lors de l’expression
des besoins et vont se traduire par des spécifications incomplètes ou encore
incohérentes. Des erreurs seront faites lors de la définition de l’architecture et
vont se traduire par une application inefficace, difficilement testable ou
encore non maintenable de sorte que toute correction d’anomalie en
entraînera de nouvelles. Enfin, des erreurs seront commises lors des phases
de codages. Toutes ces erreurs vont engendrer des défauts dans la réalisation
finale qui pourront conduire à des défaillances ou dans les cas extrêmes à des
pannes totales du système logiciel.
Quelles que soient les méthodes de conception utilisées, ces erreurs ne
peuvent être totalement évitées ; il convient donc à la fois de réduire leur
impact par la mise en place d’une architecture défensive et à la fois de viser à
leur élimination de façon aussi complète que possible, comme on peut le faire
dans le domaine chirurgical où toutes les précautions sont prises avant
l’intervention mais aussi pendant et après celle-ci. Pour cela il est nécessaire
de tester à tous les niveaux du cycle de vie des logiciels en employant une
stratégie adaptée. Lors des phases de spécification, on privilégiera des tests
dits statiques, c’est-à-dire basés sur l’analyse des différents textes produits
(au sens large). Une fois le logiciel réalisé et assemblé, on emploiera plus
fréquemment des tests dits dynamiques c’est-à-dire basés sur l’exécution du
logiciel. Parmi ces différentes étapes, la phase d’intégration est certainement
la phase la plus sensible et celle à laquelle il faudra apporter le plus grand
soin, en particulier du point de vue des tests (voir à cet effet les chapitres 6 et
11).
S’il est simple d’exprimer le besoin de tester aux différents stades de
développement, il est plus complexe de mettre en œuvre cette exigence. En
effet, tester est un problème difficile. Difficile au sens mathématique car des
résultats d’indécidabilité rendent caduc tout espoir de résoudre ce problème
de façon simple et purement automatique. Difficile également du point de vue
pratique car, même dans les cas « faisables » au sens théorique du terme, la
combinatoire associée aux différentes exécutions de n’importe quel logiciel
rend impossible une démarche brutale basée sur la seule énumération de cas
de test. Par ailleurs, les différents facteurs, humains et techniques, à prendre
en compte lors de la conception et l’exécution des tests demandent une
grande habilité aux chefs de projet et aux équipes en charge des tests.
Il faut donc mettre en place une démarche empirique, c’est-à-dire basée sur
l’expérimentation, qui permettra de définir des objectifs de test clairs (qui
seront principalement la découverte de défauts), des moyens d’observations
et qui, en s’appuyant sur des méthodes éprouvées, permettra de sélectionner
les cas de tests « pertinents ». Pour cela, le testeur pourra faire reposer son
raisonnement sur les seules spécifications, et l’on parle alors de tests boîtes
noires, ou se permettra d’utiliser en plus des spécifications des détails des
réalisations (comme le code) pour construire ses tests, et l’on parlera alors de
tests boîtes blanches. Dans ce dernier cas, il faudra prendre garde de n’utiliser
ces détails que pour exercer une instruction particulière ou parcourir un
chemin donné tout en fondant le choix de l’objet du test et du résultat attendu
sur la seule analyse des spécifications (l’expérience du testeur pourra aussi
bien entendu être utilisée avec profit). Si les détails de réalisation ne sont pas
utilisés pour déterminer les jeux de valeurs employés pour les tests, ils
peuvent être le moyen de mesurer la qualité des tests produits en caractérisant
les chemins parcourus lors de l’exécution des tests en fonction de leur
couverture des instructions, globalement ou sous certains critères liés à
l’accès aux variables. Dans certains cas particuliers, l’utilisation de code
permet néanmoins de générer des tests pertinents de façon automatique
(comme nous l’avons abordé au chapitre 9).
Tous ces aspects ont été étudiés à travers cet ouvrage ainsi que le nécessaire
besoin de mettre en place des procédures de gestion des tests incluant
l’estimation des coûts, la planification des activités, et point essentiel dans
une démarche agile de développement, une gestion en configuration des tests.
Il est en effet illusoire de vouloir employer avec profit une démarche basée
sur l’itération et l’incrémentation sans avoir une gestion de configuration
sérieuse et outillée.
Après de nombreuses années dans l’accompagnement d’industriels
confrontés à la problématique de la qualité des logiciels produits et dans le
constat que les tests demeurent un point essentiel et indispensable dans
l’amélioration ou le maintien de cette qualité nous pouvons retracer dans
cette conclusion quelques facettes saillantes de ces enjeux.
Historiquement, les tests ont eu un étrange destin dans le développement des
technologies de l’information. Dès le départ il était clair que l’on était sur un
point dur, comme on peut s’en convaincre en lisant l’histoire du projet
SAGE[1], et les conférences du NATO Science Committee qui fondèrent le
génie logiciel, en 1968-1969. Les modèles d’estimation des coûts/délais de
développement des projets logiciel, fondés sur l’analyse statistique des
données de centaines de projets, donnent dès les années 1970-1980 des
fourchettes de coût de 30-40 % pour la partie test. La NASA estime que pour
les projets spatiaux comme la navette, 70 % du coût est consacré aux tests.
On sait donc tout cela dès le départ, mais la communauté industrielle, et
surtout la communauté académique du génie logiciel, ne font rien, ou
presque. La préférence va à l’étude des langages de programmation, puis aux
outils de conception dont UML est une sorte d’aboutissement. Attitude
d’autant plus surprenante de la part de scientifiques, puisque le fondement de
la démarche scientifique est l’expérimentation. Les tests ne sont rien d’autre
que des protocoles expérimentaux dont le but est de rechercher les failles. La
validité d’une théorie – un programme est une théorie – est fondée sur
l’expérimentation. Un scientifique (cf. A. Einstein) propose une théorie, et
des dizaines, centaines, … d’autres essayent de la « falsifier », pour reprendre
la terminologie de K. Popper[2]. Le summum est atteint avec le LHC,
instrument colossal développé par le CERN, pour valider l’hypothèse de
l’existence du boson de Higgs, pierre angulaire du modèle standard des
particules et qui a donné entre l’écriture de la seconde et la troisième version
de cet ouvrage des résultats spectaculaires dans cette démonstration.
La communauté académique n’a pas initialement fait l’effort de
conceptualisation et d’abstraction concernant la validation, au sens large, des
grandes constructions que sont devenus les programmes, dès les années 1970-
1980. Les tests ont longtemps été vus comme un vaste bricolage informe dont
il vaut mieux se détourner. C’est tout le contraire de ce qui a été fait pour le
hardware, où dès le départ on s’est intéressé au « design to tests » comme une
activité indispensable et inséparable de la conception des circuits. Inutile de
concevoir ce que l’on ne saura pas valider ; on peut néanmoins noter que
depuis quelques années cette démarche a fait son chemin dans la conception
des logiciels et que la démarche théorique visant à ne voir les tests que
comme un sous domaine des preuves mathématiques est en train de laisser
place à une vision plus pragmatique tout en capitalisant sur ces efforts de
recherche anciens et variés ; voir à cet effet le chapitre 10.
Pour le logiciel, créé de toutes pièces par l’activité des programmeurs, la
présence de défauts est imputable à celui qui l’a conçu. Ce qui fait que le
statut de l’erreur est dans ce domaine complètement différent de ce que l’on
peut rencontrer dans les domaines de la mécanique ou de l’électronique. Les
matériaux que l’on trouve dans la nature contiennent des défauts, il faut faire
avec et il n’y a pas de discussion possible. De même penser que l’on peut
programmer sans erreur est une illusion que seuls ceux qui n’ont jamais
programmé peuvent imaginer. La différence entre un bon programmeur et un
mauvais programmeur n’est pas dans le nombre d’erreurs commises, mais
dans la vitesse avec laquelle les erreurs vont être diagnostiquées et
corrigées[3]. Poincaré, l’un des plus grands mathématiciens français, était
connu pour faire des erreurs, mais elles étaient faciles à corriger et ne
remettaient jamais en cause la logique générale de ses démonstrations.
La leçon à tirer est que les erreurs sont consubstantielles à l’acte de
programmation. Culpabiliser est contre-productif. Certes, il vaut mieux en
faire le moins possible, mais plus important, il faut surtout s’organiser en
conséquence. D’où les approches comme le CMMI dont nous reparlerons
plus loin.
Les seuls industriels, jusque dans les années 1980, qui se sont intéressés
sérieusement aux tests sont les constructeurs d’ordinateurs, les réalisateurs de
systèmes informatisés où les contraintes de sûreté sont fortes (défense,
spatial, aéronautique, nucléaire, etc.), les éditeurs de logiciels. Nécessité a fait
loi. Retirer un logiciel du marché parce que trop défectueux est une
expérience qui peut conduire à la faillite. La pression du risque incite à la
prudence, et finalement à la sagesse.
La démarche qualité que l’on voit se développer tout au long de la décennie
1980 met l’accent sur le préventif, en expliquant que c’est moins coûteux que
le curatif. On se souvient du titre provoquant du livre de Crosby, « Quality is
free ». Si l’on sait faire les tests qui vont éviter les défaillances lors de
l’exploitation réelle du système, c’est gagné ! Mais les industriels du milieu
défense, spatial, … sont minoritaires, et il ne parle pas le même langage que
ceux des services, banques, assurances, etc. L’informatique de gestion, les
systèmes d’information, sont souvent opposés à l'informatique industrielle,
jugée plus « scientifique ». Le message admis maintenant « qualité = test » ne
passe pas dans ces années.
Le basculement s’opère dans les années 1980 avec le développement de
l’informatique distribuée. En moins d’une décennie, les ordinateurs centraux
éclatent en centaines, puis milliers de serveurs, eux-mêmes environnés de
dizaines de milliers de postes de travail « intelligents » (des millions dans le
cas d’Internet) qui intègrent des millions de lignes de code. Le besoin
d’informaticiens explose, et il faut recruter bien au-delà des filières de
formation informatique. Le nombre d’informaticiens, dans une grande
banque, peut largement dépasser les 10 000, dont beaucoup ont été formés
« sur le tas » !
De façon subreptice, l’informatique des systèmes d’information, est devenue
aussi complexe que celle des systèmes industriels, pour d’autres raisons. Le
système d’information « vit » au rythme de l’entreprise, et il doit évoluer à la
même vitesse. Les systèmes d’information interopèrent entre eux, dans
l’entreprise, et hors de l’entreprise, formant ce que l’on appelle une
entreprise étendue. Mal maîtrisée, l’interopérabilité devient un
risque inacceptable : les pannes de l’un se propagent chez l’autre, fragilisant
les chaînes de valeur de l’entreprise. La vitesse de diagnostic des
défaillances, paramètre fondamental du contrat de service, est ralentie par
l’hétérogénéité des plates-formes de production.
Les DSI ont depuis accepté qu’il était essentiel de s’occuper sérieusement du
problème. On peut prendre comme exemple de cette prise de conscience
l’intérêt manifesté depuis quelques années de la démarche CMMI (ou de
démarches équivalentes comme COBIT, ITIL, etc.). Le cas du CMMI est
intéressant car la démarche a été initialement promue par le département
américain de la Défense, via le Software Engineering Institute, installé par le
DOD dans l’université Carnegie Mellon, à Pittsburgh. C’est une méthode, en
fait d’origine IBM, issue des milieux de la défense, qui met fortement
l’accent sur les processus d’ingénierie, et sur les actions qualité intégrées à
ces processus. Les processus de validation, vérification, tests y occupent une
place centrale. Le développement de la maturité est objectivé à l’aide
d’indicateurs permettant de mesurer les progrès accomplis. Les industriels
avec lesquels il nous a été donné de travailler (Bouygues Telecom, Orange,
PSA, pour n’en citer que deux qui ont participé ou participent à nos
enseignements au CNAM, à L’UPMC ou à Paris Nanterre) ont des processus
d’intégration, validation, vérification, test (IVVT) extrêmement fouillés et
rigoureux. C’est une condition nécessaire à la qualité des livraisons.
La relation entre l’architecture logicielle[4], et la capacité à découvrir et
corriger rapidement les erreurs commence à être mieux comprise. Les
promoteurs des méthodes agiles[5] dont un des courant est « l’eXtreme
Programming » préconisent le « Test Driven Development (TDD) » ou le
« Model Driven Development (MDD) » ou encore le « Test Driven
Development (TDD)» qui promeuvent le développement des tests avant celui
du logiciel. Dans le monde UP (Unified Process) qui accompagne UML, on
parle de processus de développement « Architecture Centric ».
L’architecture est une condition nécessaire au bon usage des outils d’aides à
la fabrication des tests. Là encore, il faut bien comprendre, et en être
convaincu, que les tests, comme les programmes, ne se fabriqueront jamais
de façon complètement automatique. Faire de bons tests est aussi difficile que
de faire de bons programmes, tant la combinatoire des cas possibles est
immense, comme on l’a vu dans cet ouvrage. C’est une autre logique qui doit
intégrer une connaissance approfondie de l’écosystème dans lequel le
programme sous test va opérer en particulier dans les systèmes embarqués de
plus en plus présents.
La profession elle-même s’organise. Les tests sont considérés comme un actif
des organisations de développement, au même titre que les programmes.
Dans une communication personnelle, la société Atos-Origin qui a réalisé le
système d’information des Jeux olympiques de Pékin, nous a communiqué
les chiffres de 200 000 heures de travail pour réaliser l’intégration du système
et 9 000 scénarios de tests pour la validation, pour garantir le bon
fonctionnement du système au jour J, le 8/08/2008, à 8h08.
Dans les projets d’intégration, très nombreux avec l’utilisation des progiciels
et/ou de la réutilisation de composants déjà développés par l’entreprise, la
part de l’IVVT devient prépondérante.
Le métier de testeur prend corps. Les organismes professionnels comme le
CIGREF, qui regroupe les DSI des grands groupes français, et le SYNTEC,
qui regroupe les sociétés de services, s’en préoccupent de plus en plus.
Ce présent ouvrage est le reflet de plus de vingt-cinq ans de pratiques et de
réflexions sur ce que l’on peut considérer comme les bases du métier de
testeur.
Notes
[1] K. Redmond, T. Smith, From Whirlwind to Mitre, The R&D story of the
SAGE Air Defense Computer, MIT Press, 2000.
[2] K. Popper, Logique de la découverte scientifique, Payot.
[3] J. Printz, Productivité des programmeurs, Hermès-Lavoisier.
[4] J. Printz, Architecture logicielle, 2e édition, Dunod, 2009.
[5] J. Printz, Écosystème des projets informatiques – Agilité et discipline,
chez Hermès-Lavoisier.
Exemple de QCM
Nous proposons dans ce chapitre quelques questions à choix multiples en
relation avec les différents chapitres du livre et couvrant le syllabus de
l’ISTQB ; ces questions peuvent être utilisées dans la préparation à la
certification ISTQB niveau fondation telle qu’elle est définie lors de la
parution de cet ouvrage. La certification, niveau fondation, contient une
quarantaine de questions auxquelles il faut répondre en 1 heure. Chaque
question ne contient qu’une bonne réponse et il n’y a pas de points négatifs.
Pour obtenir la certification il faut avoir plus de 65 % de réponses valides.
Pour le niveau avancé, il faut répondre à un questionnaire de 65 questions à
réaliser en 1 h 30 avec un niveau de difficulté associé à chaque question (de 1
à 3). Il faut obtenir plus de 65 % des points totaux.
Dans les questions qui suivent, le chiffre entre parenthèses et en italique
indiqué à la fin de l’énoncé de la question correspond au niveau de difficulté
de celle-ci. Les réponses commentées de ces questions sont disponibles à
l’adresse http://www.dunod.com/contenus-complementaires/9782100706082.
a) Tests unitaires □
b) Tests d’intégration □
c) Tests fonctionnels □
d) Revues de code □
a) Un cas de tests □
a) Un cas de tests □
□
b) Il est plus efficace de rechercher les défauts le plus tôt
possible.
d) Je suis tellement sous pression que nous ferons les tests que si
nous avons le temps en fin de projet.
□
Q24) Quelle affirmation sur les outils de test statiques est
(1)
exacte :