• Aucun résultat trouvé

Chapitre 1. Etat de l’art

1.4 Test et testabilité

1.4.3 Problématique de la localisation de fautes

La localisation de fautes ou le diagnostic de fautes, bien qu’inévitable, n’est souvent pas

identifiée parmi les activités cruciales à considérer dans les différentes phases de test au cours

de la validation et de la vérification. D’ailleurs, il est rare de voir les concepteurs introduire le

critère de diagnostic parmi les principaux critères à considérer au cours de la conception des

systèmes ou des tests. La tendance est, le plus souvent, de se focaliser sur la capacité ou

l’aptitude d’un système ou du test à révéler les fautes contenues dans le système. Il est clair

qu’on ne peut pas parler de diagnostic de fautes sans une détection préalable. Il n’en demeure

pas moins qu’une attention particulière doit être portée au diagnostic au vu des exigences de

qualité imposées par les systèmes de plus en plus complexes au cours de leur développement.

34 Chapitre 1. Etat de l’art

En effet, les activités d’exécution du système à l’aide de données de test et la détection des fautes

sont complémentaires de l’activité de diagnostic de fautes.

Le diagnostic est défini comme le processus qui détermine la partie défectueuse,

responsable d’une anomalie de fonctionnement qui a induit une défaillance [Kle93]. Une

différence entre les résultats obtenus et ceux attendus pour les entrées communiquées au

système est une défaillance. Pour déterminer qu’il y a une défaillance, il faut avoir au préalable

déterminé un oracle, c'est-à-dire la sortie attendue comme correcte à l’exécution des données

de test.

Une défaillance apporte la preuve que l’implantation du système testée est incorrecte. En

d’autres termes, une défaillance témoigne de la présence dans l’implantation du logiciel de

défauts ou fautes introduits lors de la conception ou du codage. Il s’agit alors de corriger le

défaut, après l’avoir localisé (diagnostic et correction).

Selon le contexte (test unitaire, test d’intégration et test système), il existe plus ou moins de

corrélation entre la génération ou la définition des données de test (ou tests) et le diagnostic.

L’intention du test unitaire est de définir des tests capables de détecter les fautes et permettre

leur localisation afin de les corriger. Dans ce cas, la définition des tests peut prendre en compte

l’effort de diagnostic. Quant aux phases de test d’intégration ou système, elles visent à vérifier les

fonctionnalités du système. Il est difficile, dans ce contexte, d’établir une quelconque corrélation

entre la définition des tests et le diagnostic.

En effet, même s’il est facile ou aisé de détecter des fautes au cours de l’exécution du

système sous test, il n’y a pas de raison qu’il soit facile de localiser et de corriger ces fautes

[Voa95b]. Par conséquent, l’isolation d’une faute peut nécessiter beaucoup plus de minutie et de

procédures coûteuses que la détection de faute. La mise en œuvre de solutions permettant de

réduire le coût du diagnostic peut être d’une grande importance dans la validation et la

vérification de systèmes. Les principes de base des différentes techniques de diagnostic des

systèmes ont été présentés par Sheppard et Simpson [She94].

De nombreux travaux ont été réalisés dans le domaine matériel sur la problématique du

diagnostic. Deux approches complémentaires sont généralement considérées selon que l’objectif

soit d’améliorer la « puissance » de diagnostic des tests ou d’améliorer l’analyse des résultats de

test. Dans la première approche, le but est d’améliorer les techniques d’ATPG (Automatic

Test-Pattern Generation) afin de localiser plus facilement les fautes au cours du test [Gir96, Gui91]. La

seconde approche propose l’exploitation de la structure du circuit et les résultats de test afin de

localiser les fautes au lieu d’intervenir au niveau des générateurs de tests. Cette approche de

1.4. Test et testabilité 35

diagnostic, après test, est généralement applicable aux circuits séquentiels et ne se base pas sur

une analyse du comportement du système ; excepté le cas de l’utilisation d’un « système expert »

pour le diagnostic [Xia86]. Un système expert désigne un programme qui, à partir de

connaissances intégrées, limitées et spécifiques au domaine d’application, doit réagir face à un

problème donné exactement comme l’expert du domaine.

Technique de recoupement « dicing et slicing »

La plupart des travaux portant sur la localisation des fautes en logiciel concernent la

technique de découpage (ou « slicing ») [Gal91, Hor94, Agr95, Kam95, Kor97]. Cette technique

est aussi bien utilisée pour le test logiciel que pour le diagnostic proprement dit. Le programme

est décomposé en coupes (ou slices), telles que chacune contient toutes les instructions qui

peuvent affecter la valeur d’une variable d’un programme. Parmi les différentes techniques de

diagnostic élaborées à partir des méthodes de découpage, nous nous intéressons à la technique

de recoupement.

La technique, appelée « program dicing », a été développée par Gallagher et Lyle [Gal91]. Elle

compare les coupes dynamiques, la différence entre deux coupes étant appelée « dice ». Une

coupe dynamique isole le seul chemin de données affectant une instruction pour une exécution

donnée.

Afin de localiser les fautes, on considère qu’on a deux groupes de coupes : les coupes qui

donnent les résultats incorrects et les coupes qui donnent les résultats corrects. Lorsqu’on a

deux coupes A et B telles que A est la coupe est fautive et B la coupe correcte, le dice

correspondant est (A–B), et c’est dans ce domaine restreint que les fautes sont localisées. Il s’agit

de réduire l’ensemble suspect par recoupements des résultats de l’oracle et donc de diminuer le

temps du diagnostic. La Figure 1.1 représente un programme pour tester les saveurs. La Figure

1.2 montre deux coupes et le dice résultant. La coupe A représente la variable « sucré » dans

36 Chapitre 1. Etat de l’art

N° de l’instruction Programme

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

program savoures ;

main ()

{

int rouge, vert, bleu, jaune ;

int sucré, aigre, salé, amer ;

int i ;

rouge = fetch() ;

bleu = fetch() ;

vert = fetch() ;

jaune = fetch() ;

rouge = 2 * rouge ;

sucré = rouge * vert ;

aigre = 0 ;

for (i = 0; i < rouge; i++)

aigre += vert;

sale = bleu + jaune;

vert = vert + 1;

amer = jaune + vert ;

printf (“%d %d %d %d\n”, sucré, aigre, sale, amer) ;

exit(0)

}

Figure 1.1 : Programme « savoures »

Coupe A Coupe B Dice (Coupe A – Coupe A)

main ()

{

int rouge, vert, bleu, jaune ;

int sucré, aigre, salé, amer ;

rouge = fetch() ;

vert = fetch() ;

rouge = 2 * rouge ;

sucré = rouge * vert ;

printf (“%d %d %d %d\n”, sucré, aigre, sale,

amer) ;

exit(0)

}

main ()

{

int rouge, vert, bleu, jaune ;

int sucré, aigre, salé, amer ;

vert = fetch() ;

jaune = fetch() ;

vert = vert + 1;

amer = jaune + vert ;

printf (“%d %d %d %d\n”, sucré, aigre, sale,

amer) ;

exit(0)

}

rouge = fetch() ;

rouge = 2 * rouge ;

sucré = rouge * vert ;