0% ont trouvé ce document utile (0 vote)
46 vues76 pages

Test Structurel et Graphe de Flot de Contrôle

Transféré par

Achraf Mimouna
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
46 vues76 pages

Test Structurel et Graphe de Flot de Contrôle

Transféré par

Achraf Mimouna
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Chapitre 3

Test structurel

L’implémentation est disponible : les Cas de Test (CT) et par


conséquent les Données de Test (DT) sont issues de
l’implémentation
Contrairement au test fonctionnel où les DT sont issues de la
spécification
L’implémentation sous test est modélisée par un graphe connu sous
le nom GFC (Graphe de Flot de Contrôle)

1
La structure interne du système doit être accessible

Les données de tests sont alors produites après


analyse du code

2
Le test structurel se base sur le code
• très précis, mais plus «gros» que les spécifications :
plusieurs cas à tester
• sensible aux défauts fins de programmation : chaque
cas, chaque partie peut contenir des défauts
• mais aveugle aux fonctionnalités absentes : il n’y a
pas de retour directe à la spécification

3
Par rapport au processus de test
• DT potentiellement plus fines : dépendent
des cas à choisir
• mais très nombreuses
• pas de problème de concrétisation
• difficile de définir l’oracle

4
Pourquoi faire des tests structurels ?

 On risque plus facilement de se tromper (erreur de logique,


de codage) en traitant un cas peu fréquent
 i.e., probabilité faible d’exécution et probabilité élevée
d’erreur vont souvent ensemble

5
Certains chemins qui, intuitivement, nous semblent
improbables, peuvent quand même être exécutés
Les erreurs typographiques sont :
 réparties un peu au hasard
 il est hautement probable qu’un chemin qui n’a pas été
testé en contienne

6
1. Graphe de Flot de Contrôle
Un graphe de flot de contrôle (GFC) est une représentation abstraite
et simplifiée de la structure du programme
• Décrit le flot de contrôle du programme
• Chaque nœud représente un segment de code strictement
séquentiel (sans choix ou boucle)
• Chaque arc dénote un lien de contrôle (possibilité d’aller d’un
nœud à l’autre)

7
GFC est utile pour calculer la complexité cyclomatique et déterminer le
nombre de cas de tests à définir pour assurer une couverture acceptable
• La complexité cyclomatique est une mesure de la complexité
logique d’un programme
• Dans le contexte des tests, la complexité cyclomatique indique le
nombre maximum des chemins complets indépendants, ce qui
donne le nombre de cas de tests nécessaires pour assurer une
bonne couverture du code

8
Définition formelle
Un GFC est un graphe orienté défini par un quadruplet (N, A, e, s)
où :
N est l’ensemble de sommets ou de nœuds du graphe,
A est l’ensemble d’arcs du graphe,
e est un sommet source  N dit nœud de départ ou d’entrée,
s est un sommet puit  N dit nœud d’arrivée ou de sortie.

9
Tout programme (ou algorithme) peut être transformé en un GFC
Celui-ci traduit toutes les exécutions possibles du programme

séquentialité
Construction schéma conditionnel
algorithmique
schéma itératif

Un nœud ou sommet du GFC peut traduire (ou modéliser) un bloc (ou


séquence) d’instruction

10
Un arc traduit le transfert de flot d’exécution d’un
nœud vers un autre.
Le nœud e traduit le début du flot d’exécution.
Le nœud s traduit la fin du flot d’exécution.

11
Exemple :
Soit le programme C suivant :
#include <stdio.h>
void main (void)
{
// PD
int x;
//PE
printf (x = );
scanf (%d , &x);
if (x<=0) x = -x; else x = 1-x;
if (x==-1) x=1; else x++;
printf (x = %d \n , x);
}

12
C’est le départ
printf (x = );
a
scanf (%d , &x);

b
x <= 0 x>0
x = -x; c d x = 1-x;
: transfert
e systématique
x==-1 x!=-1 (vrai)
x=1; f g x++;

h printf (x = %d \n , x);

13
a : nœud de départ
h : nœud d’arrivée

Un chemin de contrôle appartenant à un GFC traduit une


exécution en partant du nœud de départ jusqu’au nœud
d’arrivée.
Un chemin de contrôle traduit une exécution possible du
programme.
Un GFC permet de visualiser grâce au chemin de contrôle toutes
les exécutions possibles du programme sous test.

14
Illustration :
[a, b, c, e, f, h] est un chemin de contrôle.
[b, c, e, g, h] il ne s’agit pas d’un chemin de contrôle car ça
ne démarre pas du nœud de départ.
Les chemins de contrôle associés à notre programme sont :

[a, b, c, e, f, h] Ces quatre chemins traduisent toutes


[a, b, c, e, g, h] les exécutions possibles de notre
[a, b, d, e, f, h] programme
[a, b, d, e, g, h]

15
Forme algébrique
abcefh + abcegh + abdefh + abdegh
=
ab(cef + ceg + def+ deg)h
=
ab(c+ d)e(f+ g)h

16
Une opération d’addition ou de multiplication est associée à
toutes les structures primitives apparaissant dans le graphe de
flot de contrôle

Séquence : ab a b

Itération : ab(cb)*d a b c d

17
Alternative : a(b+c)d
a

b c

d
Soient deux nœuds a et b
ε représente l’absence de nœud
(ab)2 = abab exactement 2 fois
a* = ε+a+a2+a3+…
a+ = a+a2+a3+…

18
Exemple :
#define n 5
void main ()
{// partie déclaration
unsigned i, s;
int e = 28;
int a[n];
unsigned trouve;
// partie exécution
i = 0; trouve = 0;
while (!trouve)
{if (a[i] ==e) {trouve = 1; s =i;}
i++;
} // fin de la boucle
}

19
i = 0; trouve = 0
a
retour inconditionnel a : nœud d’entrée
f : nœud de sortie
b
!trouve trouve
c f Chemin de contrôle : [a, b, f]
a[i] ==e
on localise sa position
trouve = 1; a[i] !=e
d
s =i;

i++; e [ [ a, b, c, d, e, b, f] vérifier que c’est un chemin


de contrôle { a f et tous les nœuds liés par
arcs}]

20
Comment peut-on générer des DT en partant de GFC ?
Il faut sensibiliser des chemins de contrôle.

e a
DT = (e, a)
Exemple 2 avec e = 28
a = 401528 1030
trouve s

21
Définir une donnée de test (DT) permettant de sensibiliser les
chemins du GFC.
Si un chemin est sensibilisé par une DT, il est dît exécutable.
Si aucune DT ne permet de sensibiliser un chemin, il est dît
non exécutable.

22
Illustrations :
[a, b, c, d, e, b, f] est un chemin de contrôle. Toutes les exécutions

Niveau 1 : passer par tous les nœuds. Insuffisant


nœudarc a[i]!=e
Niveau 2 : passer par tous les arcs. ne sera jamais évalue

On ne peut pas dire que c’est confiant si l’algorithme


n’exécute pas toutes ces instructions.
DT = ( 28, 401528 1030 )
 Ça passe par tous les arcs.

23
Remarque :
[a, b, f]  chemin de contrôle non exécutable évident
Cas réels  le testeur se trouve face à un dilemme :
- Chemin de contrôle est réellement non exécutable.
- Incapacité (défaut d’imagination) de sensibiliser le chemin
concerné.
(partiellement exécutable : départ a  f )
( réellement exécutable : fonctionne correctement )
[Exécutable]

24
séquentialité ;
if
Constructions schéma conditionnel
algorithmiques switch
utilisées schéma itératif

while do…while for

25
Pour les programmes qui traite l’affectation des
valeurs et leur affichage par exemple (construction :
séquentialité (;)) elle admet un seul nœud comme
chemin de contrôle a

26
A propos de schéma switch :

switch (expression)
{ case valeur 1 : traitement 1;
break;
case valeur 2 : traitement 2;
break;

case valeur n : traitement n;
break;
default : traitement n+1;
break;
}
Continuer
27
expression
expression == valeur 1 expression != valeur 1 && …
expression != valeur n
expression == valeur 2
expression == valeur n

Traitement 1 Traitement 2 … Traitement n Traitement n+1

Continuer

28
for ( instruction1; expression; instruction2)
instructions3

instruction1
while (expression)
{ instruction3;
instruction2;
}

29
do do
Instruction1; corps
while (condition); while (expression);

a b corps

b expression c
vraie expression
g <= d n’est pas
condition c
g>d vraie
vraie
condition n’est
pas vraie

30
Cas : séquentialité

void main ()
{ // PE a z = x;
int x, y, z; z = x;
 a x = y;
z = x; b x = y;
y = z;
x = y;
y = z; c y = z;
}

31
x>y x≤y
a=2 a=3

if (x>y)
a = 2;
else
a = 3;

32
a x++
x<10
b
x≥10

do
x++;
while (x<10)

33
a
x<10 x≥10
x++ b

while(x<10)
x++;

34
x=1 x=2 x=3
A b B C

switch(x) {
case 1:
A;break;
case 2:
B;break;
case 3:
C;break;
}

35
2. Couverture du CFG
Pour trouver quels chemins parcourir pour couvrir le plus de
comportements du système, voici quelques critères de
couverture sur un flot de contrôle :
• Tous les nœuds : Couverture des instructions, niveau 1.
• Tous les arcs : Couverture des branchements et conditions,
niveau 2.

36
• Tous les chemins : impossible à réaliser s’il y a des
boucles. Couverture exhaustive, niveau 0. De plus,
attention, certains chemins peuvent ne pas être
atteignables.
• On peut baser des méthodes de couverture sur ces
critères.

37
a
Combien existe t’il de chemins pour un GFC ?
Se déduit directement de l’expression b

algébrique du GFC c d

- ab(c + d)e(f + g)h 1.1.(1+1).1.(1+1).1=4 e


- Fournit le nombre de chemins exécutables et
f g
non exécutables
h

38
Couverture de tous les nœuds

a
int f(int x){ x!=0
if (x!=0) x==0
x = 1; x=1 b
return 1/x;
} c return 1/x

Le chemin de contrôle abc couvre tous les nœuds


 Sensibilisé par la DT = {x=2}
 Mais pas de détection de l’erreur (division par 0)

39
Couverture de tous les arcs

Calcul de l’inverse de la somme des éléments d’un tableau entre les


indices i et j
int somme(int* t, int i, int j) int k=i;int s=0
{int k = i;int s=0; a
while(k<=j){
s+=t[k];k++; k>j
b d return 1/s
} k<=j
return 1/s; c
} s+=t[k];k++;

La DT = {t = {1,2,3}, i=0;j=2} permet de couvrir tous les arcs


Or, c’est la DT = {i=1,j=0} qui met le programme en erreur
40
Remarque sur les couvertures de niveau 0, 1 et 2 :
N0 est impossible en présence de boucles : on ne peut pas
tester tous les chemins distincts dans le cas d’une boucle
avec un grand nombre (possiblement variable) d’itérations
(chaque façon de terminer la boucle est un chemin distinct).

41
On couvrira plutôt tous les chemins indépendants ou
toutes les portions linéaires de code suivies d’un saut.
N1 n’est pas suffisant : si une instruction if ne possède pas
de branche else, on peut avoir exécuté toutes les
instructions, mais sans avoir testé ce qui arrive lorsque la
condition est fausse.
Si le programme est bien structuré (pas de goto), alors N2
implique N1.

42
o Test et couverture des instructions
 Les tests exercent les instructions exécutables potentielles dans le
code
 Le pourcentage des instructions exécutables qui ont été exécutées
par une suite de tests
o Test et couverture des décisions
 Le test de décision exerce les décisions dans le code et teste le
code qui est exécuté en fonction des résultats de la décision
 La couverture des résultats des décisions

L’obtention d’une couverture à 100 % de décisions garantit une


couverture à 100 % d’instructions (pas l’inverse)

43
Couverture de toutes les conditions :

TER2 = Nb des conditions couvertes / Nb totale des conditions

Couverture de tous les chemins exécutables :

Ce critère de couverture consiste à produire des DT de façons à couvrir tous


les chemins exécutables

44
if (x!=0) x!=0 1
x=1; x==0
y=1-x; x=1 2
3

4 y=x-1

En produisant la DT = {x=2}, nous sensibilisons le chemin [1, 2, 3, 4]


couvrons toutes les instructions du programme. C’est vrai qu’à l’aide de DT
= {x=2}, nous avons exécuté toutes les instructions mais est ce que nous
avons couvert toutes les conditions ?
La réponse est non car d’après le graphe nous constatons que la condition
x= 0 n’a pas été couverte ce qui nous ramène à penser au deuxième critère :
couverture de toutes les conditions

45
3. Tests unitaires avec JUnit

46
Soit la classe Calculator à tester.

public class Calculator {


public double add(double number1, double number2)
{return number1 + number2;}
}

Un simple script de test de la classe Calculator.

public class CalculatorTest {


public static void main(String[] args) {
Calculator calculator = new Calculator();
double result = calculator.add(10, 50);
if (result != 60)
System.out.println("Bad result: " + result);
}
}

47
Un script de test légèrement amélioré.
public class CalculatorTest {
private int nbErrors = 0;
public void testAdd() {
Calculator calculator = new Calculator();
double result = calculator.add(10, 50);
if (result != 60) throw new IllegalStateException("Bad result: " + result);
}
public static void main(String[] args) {
CalculatorTest test = new CalculatorTest();
try {test.testAdd();}
catch (Throwable e) {
test.nbErrors++;
e.printStackTrace();
}
if (test.nbErrors > 0)
throw new IllegalStateException("There were " + test.nbErrors + " error(s)");
}}

48
3.1 Création d’un programme Java

Créez le répertoire src/main/java/math.

Sauvegardez-y le fichier Addition.java.

package math;

public class Addition {


public Long calculer(Long a, Long b) {
return a+b;
}
public Character lireSymbole() {
return '-';
}
}
49
Créez le répertoire src/test/java/math.

Sauvegardez-y le fichier AdditionTest.java.

package math;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;

50
public class AdditionTest {
protected Addition op;

@Before
public void setUp()
{op = new Addition();
}

@After
public void tearDown()
{}

51
@Test
public void testCalculer() throws Exception
{
assertEquals(new Long(4), op.calculer(new Long(1), new Long(3)));
}

@Test
public void testLireSymbole() throws Exception
{
assertEquals((Character)'+', op.lireSymbole());
}
}

52
3.2 Exemple de test dans Java
Il existe plusieurs modes de calculs. Dans ce qui suit,
nous utiliserons le code suivant :

public class CompteBancaire {


protected String numeroDeCompte;

public CompteBancaire(String numeroDeCompte) {


this.numeroDeCompte = numeroDeCompte;
}

53
public String interrogerCompte(String motdepasse)
{ String resultat = null;
if (motdepasse.equals("madatedenaissance"))
{ Integer solde = calculDuSolde();
resultat = "Information du compte:";
resultat += numeroDeCompte;
resultat += ".\r\n";
resultat += "Votre compte est créditeur de ";
resultat += solde.toString();
resultat += “Euro";
} else gererErreurDeMotDePasse();
return resultat;
}

54
protected Integer calculDuSolde()
{ Integer solde = new Integer(0);
solde = 1000;
solde += 100;
solde -= 200;
solde += 400;
solde -= 300;
return solde;
}
protected void gererErreurDeMotDePasse()
{
throw new RuntimeException("Erreur non gérée correctement");
}
}
55
Nous testerons cette classe à l’aide du test suivant :
public class CompteBancaireTest
{
@Test
public void testInterrogerCompte() {
CompteBancaire compte = new CompteBancaire("0123456789");
String verif = "Information du compte:0123456789.\r\n";
verif += "Votre compte est créditeur de 1000 Euros";
assertEquals(verif,compte.interrogerCompte("madatedenaissance"));
}
}

56
3.3 Présentation de JUnit
Eclipse est un environnement de développement intégré
pour JAVA qui intègre JUnit en standard.
JUnit est un cadre pour les test unitaires (www.junit.org).
JUnit propose :
• d’écrire facilement des tests unitaires
• d’utiliser des assertions pour exprimer les oracles
• le lancement automatique de suites de test
• un formatage du diagnostic

57
58
3.3.1 Test par Assertions
• Une assertion = une confrontation avec l’oracle
• Méthodes statiques de la classe org.junit.Assert
- assertEquals
- assertTrue
- assertFalse
- assertNull
- assertNotNull
- assertSame
- assertNotSame

59
3.3.2 Méthode de test
• Méthode qui exécute un test unitaire
• Convention de nommage test [méthode à tester]()
- Mais aucune obligation (nom quelconque possible)
• Utilise l'annotation @Test
• Publique, sans paramètre, type de retour void

60
@Test
public void testSetMontant()
{
compte.setMontant(1000);
assertEquals(compte.getMontant(), 1000);
}
• L’annotation @Ignore permet d’ignorer un test

61
package dev.gayerie;

import static org.junit.Assert.*;


import org.junit.Test;

public class StringTest {

@Test
public void testString() throws Exception {
String s = "Bonjour le monde";

assertEquals("Bonjour le monde", s);


assertNotEquals("Bonsoir le monde", s);
assertFalse(s.isBlank());
}
}

62
3.3.3 Méthodes d’initialisation et de finalisation
Méthodes d'initialisation utilisées avant chaque test
• setUp() est une convention de nommage
• l'annotation @Before est utilisée

@Before
public void setUp()
{
compte = new Compte();
}

63
Méthodes de finalisation utilisées après chaque test
• tearDown() est une convention de nommage
• l'annotation @After est utilisée

@After
public void tearDown()
{
declaration = null;
}

64
3.4 Manipulations
• Cliquez sur File – New – Java Project.
• Nommez votre projet calculatrice en laissant les options par
défaut puis cliquez sur OK.
• Ajoutez un package math dans le répertoire src.
• Ajoutez la classe Addition dans le répertoire src.

65
package math;

class Addition {
public Long calculer(Long a, Long b)
{
return a+b;
}
public Character lireSymbole()
{
return '-';
}
}

66
• Ajoutez un nouveau dossier de sources nommé tests au même
niveau d’arborescence que src.
• Dans l’explorateur de paquets, faites un clic droit sur la classe
Addition.
• Dans le menu contextuel, cliquez sur New – JUnit Test Case.

Un panneau s’affiche alors :

67
68
Dans ce panneau :
• Sélectionnez le bouton radio New JUnit 4 test.
• Changez le dossier Source folder pour tests.
• Nommez la classe AdditionTest.
• Cochez les cases setUp() et tearDown().
• Dans le champ Class under test, saisissez math.Addition.
• Enfin cliquez sur Finish.

69
Eclipse va remarquer que la bibliothèque de JUnit est absente
du projet et vous propose d’ajouter automatiquement cette
dernière au projet. Dans le panneau qui apparaît, cliquez sur
OK.
Eclipse va maintenant créer automatiquement le squelette de
la classe de test :

70
package math;

import org.junit.After;
import org.junit.Before;
public class AdditionTest {

@Before
public void setUp() throws Exception { }

@After
public void tearDown() throws Exception
{
}
}
71
Il ne reste plus alors qu’à remplir cette dernière.

package math;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;

public class AdditionTest {


protected Addition op;

72
@Before
public void setUp()
{ op = new Addition(); }
@After
public void tearDown() { }
@Test
public void testCalculer() throws Exception
{assertEquals(new Long(4), op.calculer(new Long(1), new Long(3)));}
@Test
public void testLireSymbole() throws Exception
{ assertEquals((Character)'+', op.lireSymbole());}
}

73
• Dans l’explorateur de paquets, faites un clic droit sur la
classe AdditionTest.
• Dans le menu contextuel, cliquez sur Run As – JUnit test.
• Enfin, le premier rapport de tests s’affiche !

74
75
 La barre de progression est rouge, indiquant qu’au moins un
test est en échec.
 Le rapport d’erreur permet de visualiser les tests en échec et
d’afficher la cause et l’origine du problème.
 Dans ce cas, une erreur s’est glissée sur le symbole de
l’addition!

76

Vous aimerez peut-être aussi