Introduction
Les idées de test partent de modèles de fautes, des notions de fautes plausibles dans un logiciel et de la
meilleure façon de les identifier. Ces instructions montrent comment créer des expressions booléennes et
relationnelles. Elles s'attachent d'abord aux techniques par observation du code, puis expliquent comment les appliquer
si le code n'a pas encore été écrit ou s'il n'est pas disponible.
Expressions booléennes
Prenez le fragment de code suivant, issu d'un système imaginaire pour la gestion de détonation d'explosifs. Il fait
partie du système de sécurité et des contrôles vérifiant si le bouton d'activation d'une bombe fonctionne quand il est
activé.
if (publicIsClear || technicianClear) {
bomb.detonate();
}
Le code est erroné. || doit être un &&. Cette erreur a des
conséquences négatives. Au lieu d'activer la bombe quand l'expert et le public sont à l'abri, le système l'activera
quand l'un des deux seulement est à l'abri.
Quel test peut révéler ce bogue ?
Pensez à un test dans lequel le bouton est activé lorsque l'expert et le public sont à l'abri. Le code permettrait
alors la détonation de l'explosif. Toutefois, et ce point est important, le code correct (utilisant un &&) serait identique. Le test ne sert par conséquent pas à découvrir cette faute.
De la même façon, ce code incorrect se comporte correctement lorsque l'expert et le public se trouvent près de la bombe
: dans ce cas, la détonation n'a pas lieu.
Pour identifier le bogue, il vous faut une situation dans laquelle le code écrit s'évalue différemment de celui qui
aurait dû être écrit. Par exemple, le public doit se trouver à l'abri, mais l'expert en explosifs est toujours à
proximité de la bombe. Voici l'ensemble des tests regroupés dans un tableau :
publicIsClear
|
technicianClear
|
Code tel qu'écrit...
|
Code correct qui aurait...
|
|
true
|
true
|
lance la détonation
|
lancé la détonation
|
le test est inutile (pour cette faute)
|
true
|
false
|
lance la détonation
|
empêché la détonation
|
test utile
|
false
|
true
|
lance la détonation
|
empêché la détonation
|
test utile
|
false
|
false
|
ne lance pas la détonation
|
empêché la détonation
|
le test est inutile (pour cette faute)
|
Les deux tests du milieu sont utiles pour rechercher une faute déterminée. Toutefois, ils sont aussi redondants et
comme chacun identifie la faute, il est inutile de les exécuter tous les deux.
L'expression peut être incorrecte de deux façons. Ci-après deux listes d'erreurs fréquentes dans des expressions
booléennes. Les fautes à gauche sont détectées par la technique décrite ici. Celles de droite ne seront éventuellement
pas identifiées. Cette technique ne relève donc pas toutes les fautes, mais elle reste utile.
Fautes détectées
|
Fautes éventuellement non détectées
|
Utilisation d'un opérateur incorrect : a || b doit être a&&b
|
Utilisation d'une variable incorrecte : a&&b&&c
doit être a&& x&&d
|
Négation omise ou incorrecte: a||b doit être !a||b, ou ! a||b doit être a||b
|
Expression trop simple : a&&b doit être a&&b&&c
|
L'expression entre parenthèses est mal configurée : a&&b||c doit être a&&(b||c)
|
Expressions comportant plusieurs fautes dans la colonne de gauche
|
Expression trop complexe : a&&b&&c doit être a&&b
(Cette faute n'est pas si courante, mais elle est simple à détecter avec des tests servant à
d'autres fins.)
|
|
Comment ces idées sont-elles utilisées ? Imaginez une expression booléenne de type a&&!b. Vous pouvez alors établir un tableau comme suit :
a
|
b
|
a&&!b
(code tel qu'écrit)
|
doit peut-être être
a||!b
|
doit peut-être être
!a&&!b
|
doit peut-être être
a&&b
|
...
|
true
|
true
|
false
|
true
|
false
|
true
|
...
|
true
|
false
|
true
|
true
|
false
|
false
|
...
|
false
|
true
|
false
|
false
|
false
|
false
|
...
|
false
|
false
|
false
|
true
|
true
|
false
|
...
|
Si vous avez analysé toutes les possibilités, vous avez sans doute trouvé que la première, la deuxième et la quatrième
sont les seules nécessaires. La troisième expression ne détecte aucune faute de plus que les autres et est donc
inutile. Comme les expressions vont en se compliquant, il est important d'éliminer les situations superflues.
Il est clair qu'aucune personne sensée n'établirait un tel tableau. Et heureusement, vous n'avez pas à le faire. Les
cas requis sont en effet faciles à mémoriser pour des expressions simples (voir la section suivante). Pour des
expressions plus complexes comme A&&B||C, voir Idées de test pour des combinaisons de AND et de OR, qui répertorie les idées de test
pour des expressions formées de deux ou trois opérateurs. Pour des expressions encore plus complexes, vous pouvez
recourir à un programme afin de créer des
idées de test.
Tableaux pour des expressions booléennes
simples
Si l'expression est A&&B, tester avec :
A
|
B
|
true
|
true
|
true
|
false
|
false
|
true
|
Si l'expression est A||B, tester avec :
A
|
B
|
true
|
false
|
false
|
true
|
false
|
false
|
Si l'expression est A1 && A2 && ... &&
An, tester avec :
A1, A2, ..., and An ont la valeur true
|
A1 a la valeur false, le reste la valeur true
|
A2 a la valeur false, le reste la valeur true
|
...
|
An a la valeur false, le reste la valeur true
|
Si l'expression est A1 || A2 || ... || An, tester avec :
A1, A2, ..., and An ont la valeur false
|
A1 a la valeur true, le reste la valeur false
|
A2 a la valeur true, le reste la valeur false
|
...
|
An a la valeur true, le reste la valeur false
|
Si l'expression est A, tester avec :
Par conséquent, quand vous devez tester a&&!b, vous pouvez suivre le premier tableau
ci-dessus, inverser le sens b (car il est refusé) et obtenir la liste des idées de test
:
-
A true, B false
-
A true, B true
-
A false, B false
Expressions relationnelles
Voici un autre exemple de code comportant une faute :
if (finished < required) {
siren.sound();
}
< doit être un <=. Ce genre d'erreur est assez répandu. Comme pour
les expressions booléennes, vous pouvez établir un tableau des valeurs de test et voir celles qui détectent la faute :
finished
|
required
|
code tel qu'écrit...
|
le code correct aurait...
|
1
|
5
|
déclenche la sirène
|
déclenché la sirène
|
5
|
5
|
ne déclenche pas la sirène
|
déclenché la sirène
|
5
|
1
|
ne déclenche pas la sirène
|
empêché le déclenchement de la sirène
|
Plus généralement, la faute est détectable chaque fois que finished=required. A partir des
analyses de fautes plausibles, vous pouvez obtenir les règles suivantes pour des idées de test :
Si l'expression est A<B ou A>=B, testez avec ce qui suit :
A=B
|
A légèrement inférieur à B
|
Si l'expression est A>B ou A<=B, testez avec ce qui suit :
A=B
|
A légèrement supérieur à B
|
Que signifie "légèrement" ? Si A et B sont des entiers, A doit être inférieur ou supérieur d'une unité à B. S'il s'agit
de nombres à virgule flottante, A doit être assez proche de B. Il est probablement inutile qu'il soit le nombre à
virgule flottante le plus près possible de B.
Règles pour des expressions booléennes et relationnelles
combinées
La plupart des opérateurs relationnels se produisent dans des expressions booléennes, comme dans cet exemple :
if (finished < required) {
siren.sound();
}
Les règles pour les expressions relationnelles donnent ces idées de test :
-
finished est égal à required
-
finished est légèrement inférieur à cette expression : required
Les règles pour les expressions booléennes donneraient ce qui suit :
-
finished < required doit avoir la valeur true
-
finished < required doit avoir la valeur false
Si l'expression finished est légèrement inférieur à cette expression : required, finished < required ayant la valeur true, il est inutile d'écrire
la dernière.
Si cette expression a la valeur false, il est inutile de l'écrire : finished est égale à required, finished < required .
Si une expression relationnelle ne comporte aucun opérateur booléen (&& et ||), ne tenez pas compte du fait qu'il s'agit aussi d'une expression booléenne.
Les choses se compliquent avec des combinaisons d'expressions booléennes et relationnelles, comme celle-ci :
if (count<5 || always) {
siren.sound();
}
De l'expression relationnelle, vous obtenez :
-
count légèrement inférieur à 5
-
count égal à 5
De l'expression booléenne, vous obtenez :
-
count<5 true, always false
-
count<5 false, always true
-
count<5 false, always false
Il est possible de les combiner en trois autres idées de test spécifiques. Vous remarquez que count est un entier.
-
count=4, always false
-
count=5, always true
-
count=5, always false
Vous remarquez que count=5 est employé deux fois. Il serait sans doute préférable de ne
l'utiliser qu'une fois pour permettre l'emploi d'une autre valeur au lieu de tester cette expression avec deux fois le
chiffre 5 : count ? Ne vaut-il pas mieux faire un essai avec 5 et un autre avec une valeur
différente pour que le résultat soit false ? Exemple : count<5 Cette solution serait en effet
intéressante, mais également risquée car il est alors plus facile de se tromper. Imaginez que vous entrez ce qui suit :
-
count=4, always false
-
count=5, always true
-
count<5 false, always false
Imaginez qu'une faute peut uniquement être détectée avec la valeur suivante : count=5. Dans ce
cas, la valeur 5 donne "false" dans l'expression count<5, alors que le code correct aurait
donné "true". Cependant, cette valeur false est contrebalancée par la valeur always (structure OR), qui est true. La
valeur de l'expression globale est correcte, même si celle de la sous-expression relationnelle était incorrecte. La
faute n'est alors pas détectée.
La faute est en revanche détectée si l'autre count=5 est moins spécifique.
Des problèmes semblables ont lieu quand l'expression relationnelle se trouve à droite de l'opérateur booléen.
Comme il est difficile de savoir quelles sous-expressions doivent être exactes et quelles autres peuvent être
générales, le mieux est de toutes les rendre exactes. L'autre solution consiste à recourir au programme d'expressions booléennes mentionné
plus tôt. Il génère des idées de test correctes pour des expressions mixtes booléennes et relationnelles arbitraires.
Idées de test sans code
Comme expliqué dans Concept :
Concevoir les tests en premier, il est généralement préférable de concevoir des tests avant d'implémenter du code.
Par conséquent, même si les techniques obéissent à des exemples de code, elles sont généralement appliquées sans code.
Quel est le principe ?
Certains artefacts de conception, comme les diagrammes d'état-transition et les diagrammes de séquence, emploient des
expressions booléennes comme protection. Ces cas ajoutent simplement les idées de test des expressions booléennes à la
liste de contrôle d'idées de test de l'artefact. Voir Instructions pour le produit : Idées de test pour les diagrammes d'état-transition et les
diagrammes d'activité.
La situation est plus compliquée lorsque des expressions booléennes sont implicites plutôt qu'explicites. Tel est
souvent le cas dans des descriptions d'API. Voici un exemple avec la méthode suivante :
List matchList(Directory d1, Directory d1,
FilenameFilter excluder);
La description du comportement de cette méthode pourrait être comme suit :
Renvoie une liste de noms de chemins absolus de tous les fichiers figurant dans les deux répertoires. Les
sous-répertoires sont transmis. [...] Les noms de fichiers correspondant à l' exclusion sont
exclus de la liste renvoyée. L'exclusion s'applique uniquement aux répertoires de niveau supérieur, pas aux noms de
fichiers dans les sous-répertoires.
Les mots "and" et "or" n'apparaissent pas. Quand un nom de fichier est-il inclus dans la liste renvoyée ? Quand il
apparaît dans le premier répertoire et dans le second, et qu'il se trouve dans un répertoire de niveau
inférieur ou n'est pas spécifiquement exclus. Dans le code :
if (appearsInFirst && appearsInSecond &&
(inLowerLevel || !excluded)) {
add to list
}
Voici les idées de test pour cette expression sous forme de tableau :
appearsInFirst
|
appearsInSecond
|
inLower
|
excluded
|
true
|
true
|
false
|
true
|
true
|
true
|
false
|
false
|
true
|
true
|
true
|
true
|
true
|
false
|
false
|
false
|
false
|
true
|
false
|
false
|
L'approche générale pour détecter des expressions booléennes implicites dans du texte consiste à répertorier les
actions décrites (comme "renvoie un nom correspondant"). Ecrivez ensuite une expression booléenne décrivant les
situations dans lesquelles une action est réalisée. Développez des idées de test à partir de toutes les expressions.
Cette procédure laisse une place aux contradictions. Par exemple, une personne peut écrire l'expression booléenne
utilisée ci-dessus, alors qu'une autre peut considérer qu'il existe en fait deux actions distinctes : tout d'abord, le
programme trouve les noms correspondants, puis il les filtre. Par conséquent, il y a deux expressions au lieu d'une :
-
trouver une correspondance :
-
se produit quand un fichier se trouve dans le premier répertoire et qu'un autre fichier du même nom figure
dans le second répertoire
-
filtrer une correspondance :
-
se produit quand des fichiers correspondants se trouvent au niveau supérieur et que leurs noms correspondent
à l' exclusion
Ces approches peuvent donner des idées de test distinctes et donc des tests différents. Cependant, les divergences ne
sont généralement pas très importantes. Le temps passé à savoir si l'expression est correcte et à chercher d'autres
solutions doit plutôt être consacré à d'autres techniques et à la création de tests supplémentaires. Si vous souhaitez
toutefois en savoir plus au sujet de ces différences, poursuivez la lecture.
La seconde personne obtiendrait deux ensembles d'idées de test.
idées de test pour identifier une correspondance :
-
fichier dans le premier répertoire, fichier dans le second répertoire (true, true)
-
fichier dans le premier répertoire, pas de fichier dans le second répertoire (true, false)
-
pas de fichier dans le premier répertoire, fichier dans le second répertoire (false, true)
idées de test pour filtrer une correspondance (après son identification) :
-
les fichiers correspondants se trouvent au niveau supérieur, le nom correspond à l' exclusion (true, true)
-
les fichiers correspondants se trouvent au niveau supérieur, le nom ne correspond pas à l' exclusion (true, false)
-
les fichiers correspondants se trouvent à un niveau inférieur, le nom correspond à l' exclusion (false, true)
Imaginez que ces deux ensembles d'idées de test sont combinés. Les idées du second ensemble comptent uniquement lorsque
le fichier se trouve dans les deux répertoires : elles peuvent alors être combinées avec la première idée du premier
ensemble. Vous obtenez ce qui suit :
fichier dans le premier répertoire
|
fichier dans le second répertoire
|
niveau supérieur
|
correspond à l'exclusion
|
true
|
true
|
true
|
true
|
true
|
true
|
true
|
false
|
true
|
true
|
false
|
true
|
Deux idées de test pour identifier une correspondance ne figurent pas dans ce tableau. Vous pouvez les ajouter comme
suit :
fichier dans le premier répertoire
|
fichier dans le second répertoire
|
niveau supérieur
|
correspond à l'exclusion
|
true
|
true
|
true
|
true
|
true
|
true
|
true
|
false
|
true
|
true
|
false
|
true
|
true
|
false
|
-
|
-
|
false
|
true
|
-
|
-
|
Les cellules vides indiquent que les colonnes ne sont pas importantes.
Ce tableau s'apparente maintenant assez à celui de la première personne. La similitude peut être accentuée en employant
la même terminologie. Le tableau de la première personne comporte une colonne "inLower" et celui de la seconde une
colonne "in top level". Il est possible de les convertir en inversant le sens des valeurs. Dans ce cas, vous obtenez
cette version du second tableau :
appearsInFirst
|
appearsInSecond
|
inLower
|
excluded
|
true
|
true
|
false
|
true
|
true
|
true
|
false
|
false
|
true
|
true
|
true
|
true
|
true
|
false
|
-
|
-
|
false
|
true
|
-
|
-
|
Les trois premières lignes sont identiques au tableau de la première personne. Les deux dernières se distinguent
seulement car cette version n'indique pas les valeurs que mentionne la première. Ceci laisse imaginer la façon dont le
code a été écrit. La première personne a opté pour une expression booléenne compliquée :
if (appearsInFirst && appearsInSecond &&
(inLowerLevel || !excluded)) {
add to list
}
La seconde a choisi des expressions booléennes imbriquées :
if (appearsInFirst && appearsInSecond) {
// correspondance trouvée
if (inTopLevel && excluded) {
// filtrer
}
}
La différence entre les deux est que les idées de test pour la première détectent deux fautes et celles de la seconde
aucune car ces fautes ne s'appliquent pas.
-
Dans la première implémentation, il peut y avoir une faute dans les parenthèses. Les parenthèses autour de || sont-elles correctes ? Comme la seconde implémentation n'inclut pas de parenthèses et de ||, la faute est inexistante.
-
Les exigences de test pour la première implémentation vérifient si la seconde expression, &&, doit être un ||. Dans la seconde implémentation, ce && explicite est remplacé par le &&. Il n'existe aucune
faute ||-pour-&& en soi. L'imbrication est éventuellement
incorrecte, mais cette technique ne traite pas ce genre de cas.
|