Concept: Catalogue d'idées de test
Un catalogue d'idées de test répertorie les idées de test les plus aptes à faire découvrir la plupart des erreurs logicielles plausibles. Ces instructions expliquent comment créer un tel catalogue.
Relations
Eléments connexes
Description principale

Introduction

Une grande partie de la programmation consiste à prendre des choses que vous avez utilisées maintes et maintes fois par le passé et de les utiliser alors une nouvelle fois dans un contexte différent. Ces choses sont généralement certaines structures de classes-données (comme des listes chaînées, des tables de hachage ou bases de données relationnelles) ou des opérations (comme la recherche, le tri, la création de fichiers temporaires ou l'incrustation d'une fenêtre de navigateur). Par exemple, deux bases de données relationnelles client comporteront de nombreuses caractéristiques banales.

Ce qui est intéressant dans ces banalités, c'est qu'elles s'accompagnent d'erreurs banales. Les gens n'inventent pas de nouvelles manières imaginatives d'insérer quelque chose d'erroné dans une liste doublement chaînée. Ils ont tendance à faire la même erreur que les autres et eux-même ont fait auparavant. Un programmeur qui incruste une fenêtre de navigateur peut commettre l'une de ces erreurs banales :

  • crée une nouvelle fenêtre alors qu'une déjà ouverte devrait être réutilisée
  • omet de rendre visible une fenêtre de navigateur masquée ou réduite
  • utilise Internet Explorer lorsque l'utilisateur a choisi un autre navigateur par défaut
  • omet de vérifier si le JavaScript est activé

Ces erreurs étant banales, les idées de test pour les déceler le sont également. Mettez ces idées de test dans votre catalogue d'idées de test afin de pouvoir les réutiliser.

Comment un catalogue d'idées de test décèle-t-il les erreurs ?

L'un des avantages d'un catalogue qu'une seule idée de test peut être utile pour déceler plusieurs fautes sous-jacentes. Voici un exemple d'idée qui trouve deux fautes.

La première faute était dans un compilateur C. Ce compilateur prenait des options de ligne de commande telles que "-table", "-trace" ou "-nolink". Les options auraient pu être abrégées à leur forme unique la plus petite. Par exemple, "-ta" fonctionnait aussi bien que "-table". Toutefois, "-t" n'était pas autorisé en raison de son ambiguïté : cette option pouvait signifier "-table" ou "-trace".

En interne, les options de ligne de commande étaient stockées dans une table comme celle-ci :

-table
-trace
-nolink

Quand option était trouvée dans la ligne de commande, elle était recherchée dans la table. S'il elle correspondait au préfixe d'une entrée de la table, alors elle correspondait à cette entrée. Par exemple, "-t" correspondait à "-table". Une fois une correspondance trouvée, la recherche continuait dans le reste de la table pour en trouver une autre. Une autre correspondance constituerait une erreur car elle indiquerait une ambiguïté.

Le code de recherche ressemblait à cela :

for (first=0; first < size; first++)
{
  if (matches(entry[first], thing_sought))
  { /* at least one match */
    for(dup=first+1; dup < size; dup++) /* search for another */
    if (matches(entry[dup], thing_sought)) /* extra match */
      break; /* error out */
    return first;
  }
}
return -1; /* Not found or ambiguity */

Comprenez-vous où se trouve le problème ? Il est assez subtil.

Le problème provient de l'instruction d'interruption. Elle vise à interrompre la boucle enveloppante extérieure lorsqu'une correspondance double est trouvée mais elle interrompt en réalité la boucle intérieure. Cela a le même effet que si aucune seconde correspondance n'était trouvée : l'index de la première correspondance est renvoyé.

Notez que cette erreur ne peut être décelée que si l'option recherchée a deux correspondances dans la table, comme cela pourrait être le cas pour "-t".

Examinons maintenant une seconde erreur, complètement différente.

Le code prend une chaîne. Il est censé remplacer le dernier signe '=' dans la chaîne par un signe '+'. S'il n'y a aucun signe '=', rien ne se produit. Ce code utilise la routine de bibliothèque C standard suivante : strchr. En voici la teneur :

  ptr = strchr(string, '=');  /* Find last = */ if (ptr != NULL_CHAR)     *ptr = '+';

Le problème est ici aussi assez subtil.

La fonction strchr renvoie la première correspondance dans la chaîne, pas la dernière. La fonction correcte est strrchr. Le problème provenait très certainement d'une faute de frappe. (En fait, le profond problème sous-jacent est qu'il n'est absolument pas raisonnable de mettre deux fonctions qui varient uniquement d'un caractère dans une bibliothèque standard.)

Cette erreur ne peut être décelée que lorsqu'il y a au moins deux signes égal dans l'entrée. En d'autres termes :

  • "a=b" renverrait le bon résultat : "a+b".
  • "noequals" renverrait le bon résultat : "noequals".
  • "a=b=c" renverrait le mauvais résultat : "a+b=c", et non le bon : "a=b+c"

Cet exemple est intéressant et utile : il montre que deux erreurs provenant de causes complètement différentes (faute de frappe, mauvaise compréhension d'une construction C) et manifestées dans le code de façon différente (mauvaise fonction appelée, mauvaise utilisation d'une instruction de rupture) peuvent être décelées par la même idée de test (recherche d'un élément qui se produit deux fois).

Un bon catalogue d'idées de test

En quoi un catalogue est-il bon ?

  • Il contient un petit ensemble d'idées de test qui peuvent trouver un ensemble d'erreurs sous-jacentes beaucoup plus vaste.
  • Il est facile et rapide à lire (parcourir). Vous devez être capable de passer les idées de test qui ne s'appliquent pas dans votre situation.
  • Il ne contient que les idées de test que vous utiliserez. Par exemple, une personne qui ne travaille jamais avec les navigateurs Web ne devrait pas avoir à passer à chaque fois les idées de test pour les programmes qui utilisent les navigateurs Web. Une personne qui travaille sur des logiciels de jeu souhaitera un catalogue plus court que celle qui travaille sur un logiciel dans lequel la sécurité est critique. La personne qui travaille sur les jeux peut se permettre de se concentrer uniquement sur les idées de test avec la plus grande probabilité de trouver des erreurs.

Etant donné ces règles, il semble préférable d'avoir plusieurs catalogues. Lorsque certaines données et opérations sont communes à toute la programmation, leurs idées de test peuvent être consignées dans un catalogue que tous les programmeurs peuvent utiliser. Lorsqu'elles sont spécifiques à des domaines particuliers, leurs idées de test peuvent être consignées dans un catalogue spécifique à un domaine.

Le catalogue échantillon (Télécharger Adobe Reader), utilisé dans la section suivante, constitue un bon point de départ. Vous pouvez également utiliser les Idées de test pour des combinaisons de AND et de OR.

Exemple d'utilisation d'un catalogue d'idées de test

Voici comment vous pouvez utiliser le catalogue échantillon.  Supposons que vous implémentiez cette méthode :

  void applyToCommonFiles(Directory d1,         Directory d2,         Operation op);

applyToCommonFiles prend deux répertoires comme arguments. Lorsqu'un fichier du premier répertoire a le même nom qu'un fichier du second, applyToCommonFiles effectue une opération sur cette paire de fichiers. Elle traite les sous-répertoires.

Pour utiliser le catalogue, il suffit de le parcourir et de rechercher les grands titres qui correspondent à votre situation. Examinez les idées de test sous chaque titre pour voir si elles sont pertinentes puis inscrivez celles qui sont pertinentes dans une Liste d'idées de test.

Remarque : cette description étape par étape peut rendre laborieuse l'utilisation du catalogue. Il faut plus de temps pour lire les instructions de création d'une liste de contrôle que pour la créer.

Ainsi, dans le cas de la méthode applyToCommonFiles, vous pouvez appliquer le catalogue selon la description qui suit.

La première entrée est pour N'importe quel objet. Des arguments peuvent-ils être des pointeurs nuls ? C'est une question de contrat entre la méthode applyToCommonFiles et ses appelants. Le contrat peut établir que les appelants ne passeront pas dans un pointeur nul. S'ils le font, vous ne pouvez pas compter sur le comportement prévu : applyToCommonFiles pourrait effectuer n'importe quelle action. Dans ce cas, aucun test n'est approprié car aucune action de la méthode applyToCommonFiles ne peut être incorrecte. Cependant, si applyToCommonFiles était requis pour vérifier les pointeurs nuls, l'idée de test serait utile. Si l'on part de ce principe, cela nous donne la liste d'idées de test de départ :

  • d1 est nul (cas d'erreur)
  • d2 est nul (cas d'erreur)
  • op est nul (cas d'erreur)

L'entrée du catalogue suivante est Chaînes. Les noms des fichiers sont des chaînes et sont comparés pour voir s'ils correspondent. L'idée d'effectuer un test avec la chaîne vide ("") ne paraît pas utile. Il se peut que des routines standard de comparaison de chaînes soient utilisées, et qu'elles traitent correctement les chaînes vides.

A propos... Si des chaînes sont comparées, qu'en est-il de la casse ? Supposons que d1 contienne un fichier nommé "File". d2 contient également un fichier nommé "file". Ces fichiers doivent-ils correspondre ? Sur UNIX, il est clair que non. Sur Microsoft Windows, il est pratiquement certain que oui. C'est une autre idée de test :

  • Les fichiers correspondent dans les deux répertoires, mais la casse des noms est différente.

Remarquez que cette idée de test n'est pas venue directement du catalogue. Toutefois, le catalogue a attiré notre attention sur un aspect particulier du programme (noms de fichier comme chaînes) et notre créativité nous a donné l'idée en plus. Il est important de ne pas utiliser le catalogue de trop près, utilisez-le comme une source de réflexion et d'inspiration pour de nouvelles idées.

L'entrée suivante est Collections. Un répertoire est une collection de fichiers. De nombreux programmes qui gèrent les collections échouent lorsqu'une collection est vide. Un petit nombre de programmes qui gère les collections vides, ou les collections contenant de nombreux éléments, échoue lorsqu'il n'y a qu'un seul élément. Ces idées sont donc utiles :

  • d1 est vide
  • d2 est vide
  • d1 a exactement un fichier
  • d2 a exactement un fichier

L'idée suivante est d'utiliser une collection de la taille maximum possible. applyToCommonFiles serait normalement utilisée sur de petits répertoires. Ensuite, un utilisateur applique à ces répertoires deux énormes arborescences contenant des milliers de fichiers et découvre alors que le programme a une mémoire ridicule et qu'il ne peut pas gérer ce cas réaliste.

Ceci posé, il n'est pas important de tester la taille maximum absolue d'un répertoire ; il suffit qu'il s'agisse d'un répertoire aussi gros que celui que pourrait avoir un utilisateur. Cependant, il faut au moins un test avec plus de trois fichiers dans un répertoire :

  • d1 contient un très grand nombre de fichiers
  • d2 contient un très grand nombre de fichiers

L'idée de test finale (éléments doubles) ne s'applique pas aux répertoires de fichiers. Si dans un répertoire, deux fichiers ont le même nom, le problème ne vient pas de la méthode applyToCommonFiles: c'est votre système de fichiers qui est corrompu.

L'entrée du catalogue suivante est Recherche. Ces idées peuvent être traduites comme suit, en termes de méthode applyToCommonFiles :

  • d1 et d2 n'ont aucun fichier en commun (tous les noms sont différents)
  • d1 et d2 ont exactement un fichier en commun (c'est le dernier élément du répertoire dans l'ordre alphabétique)
  • d1 et d2 ont plus d'un fichier en commun

L'idée de test finale vérifie la méthode applyToCommonFiles. Renvoie-t-elle la première correspondance dès qu'elle en trouve une ? La remarque entre parenthèses dans l'idée de test ci-dessus part du principe que le programme trouvera la liste de fichiers dans un répertoire à l'aide d'une routine de bibliothèque qui les renvoie, triés par ordre alphabétique. Sinon, il peut être préférable d'utiliser le dernier élément comme correspondance. Avant de consacrer beaucoup de temps à comprendre la manière dont les fichiers sont ordonnés, demandez-vous quel serait le taux de probabilité de détection d'incidents si vous placiez en dernier l'élément correspondant. Mettre un élément en dernier dans une collection est plus utile si le code explore explicitement étape par étape la collection à l'aide d'un index. S'il utilise un itérateur, il est très improbable que l'ordre importe.

Examinons une autre entrée du catalogue échantillon. L'entrée structures liées nous rappelle que nous comparons des arborescences de répertoires, pas seulement de simples collections de fichiers. Le choix d'un mode de test pour la méthode applyToCommonFiles nous oblige à faire face au caractère inachevé de sa description.

Si la structure du répertoire est la suivante :

Diagramme de structure d'un répertoire

Figure 1 : une structure de répertoire

La méthode applyToCommonFiles traite-t-elle le répertoire Cdir? Cela ne semble pas logique. Il ne peut y avoir aucune correspondance avec aucun élément situé dans l'autre arborescence de répertoires. En fait, il semble que les fichiers des sous-répertoires ne peuvent correspondre que si les noms des sous-répertoires correspondent. C'est-à-dire, supposons que nous ayons cette structure de répertoire :

Second diagramme de structure d'un répertoire

Figure 2 : une seconde structure de répertoire

Les fichiers nommés "File" ne correspondent pas entre eux car ils se trouvent dans des sous-répertoires différents. Les sous-répertoires devraient être traités uniquement s'ils ont le même nom dans les deux emplacements : d1 d2. Cela amène à ces idées de test :

  • un sous-répertoire dans d1 est introuvable dans d2 (non traité)
  • un sous-répertoire dans d2 est introuvable dans d1 (non traité)
  • un sous-répertoire apparaît dans d1 et dans d2 (traité)

Mais cela soulève d'autres questions. L'opération (op) doit-elle être appliquée aux sous-répertoires correspondants ou seulement aux fichiers correspondants ? Si elle est appliquée aux sous-répertoires, doit-elle l'être avant de descendre l'arborescence ou après ? La différence entre les deux est importante si, par exemple, l'opération efface le fichier ou répertoire correspondant. Pour cela, est-ce que l'opération doit être autorisée à modifier la structure du répertoire ? Et plus précisément : quel est le comportement correct de la méthode applyToCommonFiles si tel est le cas ? (Le même problème se présente avec les itérateurs.)

Les questions de ce genre se posent généralement lorsque vous lisez attentivement la description d'une méthode concernant la création d'idées de test. Mais laissons-les de côté pour le moment. Quelles que soient les réponses, il leur faudra des idées de test qui vérifient que le code implémente correctement les réponses.

Revenons au catalogue. Nous n'avons toujours pas considéré toutes ses idées de test. La première - vide (rien dans la structure)- demande une répertoire vide. Nous avons déjà eu cela avec l'entrée Collections. Nous avons également eu la structure minimale non-vide, qui est un répertoire avec un seul élément. Ce genre de redondance n'est pas rare, mais il est facile de passer à côté.

Et une structure circulaire ? Les structures de répertoire ne peuvent pas être circulaires, un répertoire ne peut pas être un de ses descendants ou dans lui-même... si ? Et les raccourcis (sous Windows) ou les liens symboliques (sous UNIX) ? S'il existe un raccourci dans l'arborescence de d1qui pointe vers d1, la méthode applyToCommonFiles devrait-elle continuer à descendre l'arborescence indéfiniment ? La réponse pourrait mener à une ou plusieurs nouvelles idées de test :

  • d1 est circulaire en raison de raccourcis ou liens symboliques
  • d2 est circulaire en raison de raccourcis ou liens symboliques

En fonction du comportement approprié, il peut y avoir plus d'idées de test que cela.

Enfin, qu'en est-il de la profondeur supérieure à un ? Les idées de test précédentes garantiront que nous testons la descente de l'arborescence jusqu'à un niveau de sous-répertoire, mais nous devons vérifier que la méthode applyToCommonFiles continue de descendre l'arborescence :

  • sur plusieurs niveaux (>1) des sous-répertoires de d1
  • sur plusieurs niveaux (>1) des sous-répertoires de d2

Création et maintenance de votre propre catalogue d'idées de test

Comme indiqué plus haut, le catalogue générique ne contiendra pas toutes les idées de test dont vous avez besoin. Mais des catalogues spécifiques au domaine n'ont pas été rendus publics par les entreprises qui les ont créés. Si vous les voulez, vous devrez les faire vous-même. Voici quelques conseils.

  • Ne remplissez pas un catalogue d'hypothèses potentiellement utiles à la découverte d'erreurs. Souvenez-vous que chaque idée de test que vous consignez dans le catalogue vous coûte du temps et de l'argent :
    • votre propre temps pour maintenir le catalogue
    • le temps d'autres programmeurs pour réfléchir à l'idée de test
    • et éventuellement le temps d'autres programmeurs pour implémenter un test

    N'ajoutez que les idées qui ont fait leurs preuves. Vous devriez pouvoir au moins signaler une erreur réelle que l'idée de test a décelée. Dans l'idéal, l'erreur doit avoir été omise par d'autres tests ; elle doit avoir été signalée sur le terrain. Une bonne façon de créer des catalogues est de parcourir la base de données de bogues de l'entreprise et de s'interroger sur la façon dont chaque erreur aurait pu être détectée plus tôt.


  • Le catalogue d'idées de test a peu de chances de fonctionner si sa création et sa maintenance relèvent du passe-temps pour vous. Vous aurez besoin de temps spécialement consacré à cette tâche, comme pour toute autre tâche d'importance. Nous vous recommandons de créer et de maintenir votre catalogue d'idées de test au cours de l'Activité : Amélioration des actifs de test.