Instructions: Idées de test pour des expressions booléennes et des conditions frontière
Les idées de test se fondent sur des fautes plausibles d'un logiciel et sur la meilleure façon de les découvrir. Ces instructions expliquent comment développer des idées de test pour des expressions booléennes et des conditions frontière.
Relations
Eléments connexes
Description principale

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 :

A

true

false


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 :

  1. finished est égal à required
  2. finished est légèrement inférieur à cette expression : required

Les règles pour les expressions booléennes donneraient ce qui suit :

  1. finished < required doit avoir la valeur true
  2. 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.

  1. count=4, always false
  2. count=5, always true
  3. 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 :

  1. count=4, always false
  2. count=5, always true
  3. 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.

  1. 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.
  2. 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.