Tests détaillés

Tests de couverture

Le test de couverture du code commence avec l'introduction d'instructions spéciales dans le code du programme, quelquefois par un préprocesseur, quelquefois par un modificateur de code objet, quelquefois en utilisant un mode spécial du compilateur ou de l'éditeur de liens, pour suivre tous les chemins possible dans un bloc de code source et d'enregistrer, pendant leur exécution, tous ceux qui ont été parcourus.

Examinons l'extrait de code C, tout à fait typique, suivant :

1.   if (read(s, buf, sizeof buf) == -1)
2.   error++; 
3.   else
4.   error = 0; 
    
Ce code présente un défaut si la variable « error » n'a pas été initialisée, et si la ligne 2 est exécutée les résultats seront imprévisibles. La probabilité d'apparition d'une erreur dans une instruction « read » (et d'obtenir une valeur de retour égale à -1) lors de tests normaux est assez faible. La manière d'éviter la maintenance coûteuse de ce genre de bogue consiste à s'assurer que les tests unitaires traitent tous les chemins possibles dans le code et que les résultats sont corrects dans tous les cas.

Mais il y a mieux : les chemins possibles dans le code se combinent. Dans notre exemple ci-dessus, la variable d'erreur peut avoir été initialisée avant, disons par un morceau de code similaire dont le prédicat (« échec de l'appel système ») était faux (signifiant l'apparition de l'erreur). L'exemple suivant, dont le code est manifestement incorrect et qui n'aurait pas surmonté un examen, montre la facilité avec laquelle des choses simples peuvent devenir compliquées :

1.   if (connect(s, &sa, &sa_len) == -1)
2.   error++;
3.   else
4.   error = 0;
5.   if (read(s, buf, sizeof buf) == -1)
6.   error++;
7.   else
8.   error = 0;

Il existe maintenant quatre chemins à tester dans le code :

Il est en général impossible de tester tous les chemins possibles dans le code, il peut y en avoir des centaines, même dans une petite fonction de quelques dizaines de lignes. Et d'un autre côté, il n'est pas suffisant de s'assurer uniquement que les tests unitaires sont capables (éventuellement en plusieurs fois) de mettre à l'épreuve toutes les lignes de code. Ce type d'analyse de couverture ne fait pas partie de la trousse à outils de tous les ingénieurs logiciel sur le terrain, c'est pourquoi l'assurance qualité (AQ) en fait sa spécialité.

Tests de non-régression

Corriger une erreur n'est pas suffisant. L'expression « évident à l'examen » constitue souvent une réponse destinée à cacher celle, plus insidieuse, affirmant « il serait difficile d'écrire un test qui pète le feu ». Bon, oui, de nombreux bogues sautent aux yeux lors d'un examen, comme la division par la constante zéro. Mais pour savoir quoi corriger, il faut examiner le code environnant pour comprendre les intentions de l'auteur. Ce genre d'analyse doit être documenté dans la correction ou dans les commentaires accompagnant le code source.

Dans le cas le plus fréquent, le bogue n'est pas évident à l'examen et la correction se trouve à un endroit différent, dans le code source, de celui où le programme provoque une erreur fatale ou a un comportement inadéquat. Dans ces circonstances, il faut écrire un nouveau test qui met à l'épreuve la partie de code défaillant (ou qui met le programme dans un état incorrect ou autre) puis il faut tester la correction avec ce nouveau test unitaire. Après revue et enregistrement, le nouveau test unitaire doit également être enregistré de façon à ce que si le même bogue est réintroduit plus tard comme effet secondaire de quelque autre modification, l'AQ ait quelque espoir de le trouver avant les clients.