Académique Documents
Professionnel Documents
Culture Documents
Sébastien Bardin
sebastien.bardin@cea.fr
http://sebastien.bardin.free.fr
Compétences à aquérir
Introduction
Automatisation de la génération de tests
Critères de test avancés
Introduction
◮ Contexte
◮ Définition du test
◮ Point de vue industriel
◮ Aspects pratiques
◮ Discussion
Automatisation de la génération de tests
Critères de test avancés
Domains critiques
atteindre le (très haut) niveau de qualité imposée par les
lois/normes/assurances/... (ex : DO-178B pour aviation)
Autres domaines
atteindre le rapport qualité/prix jugé optimal (c.f. attentes du client)
Quelles méthodes ?
revues
simulation/ animation
tests méthode de loin la plus utilisée
méthodes formelles encore très confidentielles, même en syst. critiques
Coût de la V & V
10 milliards $/an en tests rien qu’aux US
plus de 50% du développement d’un logiciel critique
(parfois > 90%)
en moyenne 30% du développement d’un logiciel standard
Introduction
◮ Contexte
◮ Définition du test
◮ Aspects pratiques
◮ Discussion
Automatisation de la génération de tests
Critères de test avancés
Tester, c’est exécuter le programme dans l’intention d’y trouver des anomalies
ou des défauts
- G. J. Myers (The Art of Software Testing, 1979)
Beware of bugs in the above code ; I have only proved it correct, not tried it.
- Donald Knuth (1977)
It has been an exciting twenty years, which has seen the research focus evolve
[. . .] from a dream of automatic program verification to a reality of
computer-aided [design] debugging.
- Thomas A. Henzinger (2001)
Tests de régression : vérifier que les corrections / évolutions du code n’ont pas
introduits de bugs
Se base sur le code : très précis, mais plus “gros” que les spécifications
Bug du Pentium : cause = erreurs sur quelques valeurs (parmis des millions)
dans une table de calculs
impossible à trouver en test boite noire
aurait pu être trouvé facilement en test unitaire du module concerné
Introduction
◮ Contexte
◮ Définition du test
◮ Point de vue industriel
◮ Aspects pratiques
◮ Discussion
Automatisation de la génération de tests
Critères de test avancés
Sécurité
domaines : OS, navigateurs, hyperviseurs / VM / sandbox, briques de
chiffrement, etc.
utilisation massive du fuzzing en test de vulnérabilité
test boite noire, essentiellement random, oracle pass/crash
Utilisation courante
xUnit et assimilés [régression]
outils autour de xUnit [calcul de couverture, stubs]
analyse dynamique (ex : Valgrind) [oracle automatisé un peu meilleur que
pass/crash]
Plus confidentiel
outils pour calcul de couverture avancée (ex : Mu-Java, Frama-C/LTest)
[calcul de couverture]
spécifications exécutables (jml, CodeContract, e-ACSL)
[oracle automatisé + puissant]
détection d’objectifs de test infaisables (FramaC/LTest)
[aide génération, meilleur calcul de couverture]
Introduction
◮ Contexte
◮ Définition du test
◮ Point de vue industriel
◮ Aspects pratiques
◮ Discussion
Automatisation de la génération de tests
Critères de test avancés
oracle
script
tests de régression et JUnit
critères de couverture
oracle
script
tests de régression et JUnit
critères de couverture
oracle
script
tests de régression et JUnit
critères de couverture
Quand ?
en phase de maintenance / évolution
ou durant le développement
JUnit offre :
des primitives pour créer un test (assertions)
des primitives pour gérer des suites de tests
des facilités pour l’exécution des tests
statistiques sur l’exécution des tests
interface graphique pour la couverture des tests
points d’extensions pour des situations spécifiques
Dans les méthodes de test unitaire, les méthodes testées sont appelées et leur
résultat est testé à l’aide d’assertions :
assertEquals(a,b)
teste si a est éqal à b (a et b sont soit des valeurs primitives, soit des
objets possédant une méthode equals)
assertTrue(a) et assertFalse(a)
testent si a est vrai resp. faux, avec a une expression booléenne
assertSame(a,b) et assertNotSame(a,b)
testent si a et b référent au même objet ou non.
assertNull(a) et assertNotNull(a)
testent si a est null ou non, avec a un objet
fail(message)
si le test doit echouer (levée d’exception)
oracle
script
tests de régression et JUnit
critères de couverture
Deux enjeux :
DT suffisamment variées pour espérer trouver des erreurs
maı̂triser la taille : éviter les DT redondantes ou non pertinentes
oracle
script
tests de régression et JUnit
critères de couverture
◮ boite noire
◮ boite blanche
Principe :
diviser le domaine des entrées en un nombre fini de classes tel que le
programme réagisse pareil (en principe) pour toutes valeurs d’une classe
conséquence : il ne faut tester qu’une valeur par classe !
⇒ permet de se ramener à un petit nomlbre de CTs
Procédure :
1. Identifier les classes d’équivalence des entrées
◮ Sur la base des conditions sur les entrées/sorties
◮ En prenant des classes d’entrées valides et invalides
2. Définir des CT couvrant chaque classe
interface-based
basée uniquement sur les types des données d’entrée
facile à automatiser ! (cf. exos)
functionality-based
prend en compte les relations entre variables d’entrées
plus pertinent
peu automatisable
Le test des valeurs limites est une tactique pour améliorer l’efficacité des DT
produites par d’autres familles.
Idée : les erreurs se nichent dans les cas limites, donc tester aussi les valeurs
aux limites des domaines ou des classes d’équivalence.
test partitionnel en plus agressif
plus de blocs, donc plus de DT donc plus cher
Stratégie de test :
Tester les bornes des classes d’équivalence, et juste à côté des bornes
Tester les bornes des entrées et des sorties
Exemples :
soit N le plus petit/grand entier admissible : tester N − 1, N, N + 1
ensemble vide, ensemble à un élément
fichier vide, fichier de taille maximale, fichier juste trop gros
string avec une requête sql intégrée
...
Si on a plusieurs entrées :
dans le cas où il y a trop de bi ,j , le nombre de partitions Πbi ,j explose et la
technique devient inutilisable
Hypothèse sous-jacente :
majorité des fautes détectées par des combinaisons de 2 valeurs de
variables
◮ semble ok en pratique
oracle
script
tests de régression et JUnit
critères de couverture
◮ boite noire
◮ boite blanche
1 START
2 input ( i )
3 sum := 0
4 l o o p : i f ( i > 5 ) goto end
5 input ( j )
6 i f ( j < 0 ) goto end
7 sum := sum + j
8 i f ( sum > 1 0 0 ) goto end
9 i := i +1
10 goto l o o p
11 end : HALT
Définition
critère DC
ET les tests doivent montrer que chaque condition atomique peut
influencer la décision :
par exemple, pour une condition C = a ∧ b, les deux DT a = 1, b = 1 et
a = 1, b = 0 prouvent que b seul peut influencer la décision globale C
Exercice : supposons que TS2 couvre CT2 et trouve un bug sur P, et TS1
couvre un critère CT1 tq CT1 CT2.
Question : TS1 trouve-t-il forcément le même bug que TS2 ?
Livres
Introduction
◮ Contexte
◮ Définition du test
◮ Aspects pratiques
◮ Complément test de régression
◮ Discussion
Automatisation de la génération de tests
Critères de test avancés
Compromis entre tout rejouer (sûr mais trop cher) et ne pas rejouer assez.
certains tests ne passent pas par les modifications : les ignorer
Introduction
◮ Contexte
◮ Définition du test
◮ Point de vue industriel
◮ Aspects pratiques
◮ Discussion
Automatisation de la génération de tests
Critères de test avancés
Difficile
trouver les défauts = pas naturel (surtout pour le programmeur)
qualité du test dépend de la pertinence des cas de tests
Testing can only reveal the presence of errors but never their absence
- E. W. Dijkstra (Notes on Structured Programming, 1972)
Testing can only reveal the presence of errors but never their absence
- E. W. Dijkstra (Notes on Structured Programming, 1972)
Testing can only reveal the presence of errors but never their absence
- E. W. Dijkstra (Notes on Structured Programming, 1972)
Testing can only reveal the presence of errors but never their absence
- E. W. Dijkstra (Notes on Structured Programming, 1972)
Testing can only reveal the presence of errors but never their absence
- E. W. Dijkstra (Notes on Structured Programming, 1972)
Testing can only reveal the presence of errors but never their absence
- E. W. Dijkstra (Notes on Structured Programming, 1972)
Beware of bugs in the above code ; I have only proved it correct, not tried it.
- Donald Knuth (1977)
It has been an exciting twenty years, which has seen the research focus evolve
[. . .] from a dream of automatic program verification to a reality of
computer-aided [design] debugging.
- Thomas A. Henzinger (2001)
Introduction
Automatisation de la génération de tests
Critères de test avancés
Introduction
Automatisation de la génération de tests
◮ Prédicat de chemins
◮ Exécution symbolique
◮ Exécution concolique
◮ En pratique
◮ Discussion
◮ Complément : Aspects logiques
◮ Complément : Optimisations
Critères de test avancés
Prédicat de chemin
Un prédicat de chemin pour π (dans T ) est une formule logique ϕπ ∈ T
interprétée sur D telle que si V |= ϕπ alors l’exécution du programme sur V
suit le chemin π, c’est à dire que π P(V ).
Loc Instruction
0 input(y,z)
1 w := y+1
2 x := w + 3
3 if (x < 2 * z) (branche True)
4 if (x < z) (branche False)
Loc Instruction
0 input(y,z)
1 w := y+1
2 x := w + 3
3 if (x < 2 * z) (branche True)
4 if (x < z) (branche False)
Loc Instruction
0 input(y,z)
1 w := y+1
2 x := w + 3
3 if (x < 2 * z) (branche True)
4 if (x < z) (branche False)
Loc Instruction
0 input(y,z)
1 w := y+1
2 x := w + 3
3 if (x < 2 * z) (branche True)
4 if (x < z) (branche False)
Loc Instruction
0 input(y,z)
1 w := y+1
2 x := w + 3
3 if (x < 2 * z) (branche True)
4 if (x < z) (branche False)
Loc Instruction
0 input(y,z)
1 w := y+1
2 x := w + 3
3 if (x < 2 * z) (branche True)
4 if (x < z) (branche False)
t
wpre(t, X ′ ) : ensemble X ⊆ D tq ∀x ∈ X , ∀y tq x −
→ y alors y ∈ X ′
ensemble des éléments dont tous les successeurs par t sont dans X’
wpre(t, ϕ′ ) : formule ϕ tq JϕK = wpre(t, Jϕ′ K)
t
post(X , t) : ensemble X ′ ⊆ D tq ∀y ∈ X ′ , ∃x ∈ X telque x −→y
ensemble des éléments ayant au moins un prédécesseur par t dans X
post(ϕ, t) : formule ϕ′ tq Jϕ′ K = post(JϕK , t)
1 2 n t t t
Soit un chemin du programme P : π =−
→ −
→ ... −
→
Alors
le prédicat de chemin le plus faible de π est défini par :
ϕ¯π = wpre(t1 , wpre(t2 , . . . wpre(tn , ⊤)))
conséquence : un prédicat de chemin quelconque ϕπ pour π vérifie :
ϕπ ⇒ ϕ¯π
ϕ¯π ′ = post(post(post(⊤, t1 ), t2 ) . . . , tn )
relation : ϕ¯π ′ ⇒ ϕ¯π et (ϕ¯π ′ , ϕ¯π ) équisatisfiable
Introduction
Automatisation de la génération de tests
◮ Prédicat de chemins
◮ Exécution symbolique
◮ Exécution concolique
◮ En pratique
◮ Discussion
◮ Complément : Aspects logiques
◮ Complément : Optimisations
Critères de test avancés
procedure Search(node, π, Φ)
input : CFG node, path prefix π, path predicate Φ for π
output : no result, update Tests
1: Case node of
2: | halt → /* end node */
3: try Sp := solve(Φ) ; Tests := Tests + {(Sp , π)} /* new TD */
4: with unsat → () ;
5: end try
6: | block i → Search(node.next, π · node, Φ ∧ symb(i))
7: | goto tnode → Search(tnode, π · node, Φ)
8: | ite(cond,inode,tnode) → /*branching*/
9: Search(inode, π · node,Φ ∧ symb(cond)) ;
10: Search(tnode, π · node,Φ ∧ ¬symb(cond))
11: end case
termF : := k ∈ N | VF
| termF +F termF | termF −F termF | termF ×F termF
Pourquoi α(Vc ) :
les “variables” du programme C peuvent être modifiées à chaque étape de
l’exécution
les “variables” de la théorie T sont des inconnues, de valeur constante
le renommage est nécessaire pour prendre en compte la dynamique de
l’exécution
◮ prédicat de chemin pour x := x+1 ?
◮ Xn+1 = Xn + 1, plutôt que X = X + 1
Propriétés
Correction si le calcul symbolique est correct, alors la procédure est correcte : chaque
DT généré suit le chemin prévu
Complétude si le calcul symbolique est complet, alors la procédure est complète :
quand la procédure termine, chaque chemin faisable est couvert
Terminaison la procédure termine ssi le nombre de chemins est fini
L’exécution concolique apporte des solutions aux 2 derniers problèmes (cf après)
Problème
Une exécution symbolique génère une expression symbolique de type
z =x ∗x
Cette expression n’est pas solvable en arithmétique linéaire
Introduction
Automatisation de la génération de tests
◮ Prédicat de chemins
◮ Exécution symbolique
◮ Exécution concolique
◮ En pratique
◮ Discussion
◮ Complément : Aspects logiques
◮ Complément : Optimisations
Critères de test avancés
X<0 X >= 0
X <> 12 X = 12
X >= -3 X < -3
concret : X=12
backtrack + résolution, solution X = 5
concret : X=5
backtrack + résolution, unsat
S.Bardin Test Logiciel 119/ 198
Suivre seulement des chemins faisables
X<0 X >= 0
X <> 12 X = 12
X >= -3 X < -3
concret : X=12
backtrack + résolution, solution X = 5
concret : X=5
backtrack + résolution, unsat
S.Bardin Test Logiciel 119/ 198
Suivre seulement des chemins faisables
X<0 X >= 0
X <> 12 X = 12
X >= -3 X < -3
concret : X=12
backtrack + résolution, solution X = 5
concret : X=5
backtrack + résolution, unsat
S.Bardin Test Logiciel 119/ 198
Suivre seulement des chemins faisables
X<0 X >= 0
X <> 12 X = 12
X >= -3 X < -3
concret : X=12
backtrack + résolution, solution X = 5
concret : X=5
backtrack + résolution, unsat
S.Bardin Test Logiciel 119/ 198
Suivre seulement des chemins faisables
X<0 X >= 0
X <> 12 X = 12
X >= -3 X < -3
concret : X=12
backtrack + résolution, solution X = 5
concret : X=5
backtrack + résolution, unsat
S.Bardin Test Logiciel 119/ 198
Suivre seulement des chemins faisables
X<0 X >= 0
X <> 12 X = 12
X >= -3 X < -3
concret : X=12
backtrack + résolution, solution X = 5
concret : X=5
backtrack + résolution, unsat
S.Bardin Test Logiciel 119/ 198
Suivre seulement des chemins faisables (2)
procedure Search(n, π, Φ, C)
1: Case n of
2: | halt → () /* end node */
3: | block i → Search(n.next, π · n, Φ ∧ symb(i), update(C,i))
4: | goto n’ → Search(n’, π · n,Φ, C)
5: | ite(cond,in,tn) →
6: Case eval(cond,C) of /* follow concrete branch */
7: | true →
8: Search(in, π · n, Φ ∧ SYMB(cond), C) ;
9: try /* solve new branch first */
10: Sp := solve(Φ ∧ ¬cond) ; Tests := Tests + {(Sp , π.tn)}
11: C ′ := update C for branching(Sp )
12: Search(tn, π · n,Φ ∧ ¬SYMB(cond),C’) /* branching */
13: with unsat → ()
14: end try
15: | false → ..... /* symmetric case */
16: end case
17: end case
S.Bardin Test Logiciel 123/ 198
Procédure concolique basique (2)
Plutôt récent
déjà dans PathCrawler (CEA, 2004) pour chemins infaisables
popularisé par DART et CUTE (2005) pour robustesse
Introduction
Automatisation de la génération de tests
◮ Prédicat de chemins
◮ Exécution symbolique
◮ Exécution concolique
◮ En pratique
◮ Discussion
◮ Complément : Aspects logiques
◮ Complément : Optimisations
Critères de test avancés
Études de cas académiques sur des codes type drivers / kernel (Linux, BSD,
Microsoft .NET)
codes souvent déjà bien testés, nombreux bugs trouvés
ex : Klee : > 95 % de couverture obtenue automatiquement sur les Unix
coreutils (make, grep, wc, etc.)
PathCrawler online
http ://pathcrawler-online.com/
En pratique :
la prise en compte de la précondition à la génération est essentielle
(correction des tests)
filtrage a posteriori pas satisfaisant
pour l’oracle, la prise en compte à la génération est lourde et pas
forcément couronnée de succès
◮ filtrage a posteriori si le but est de considérer un oracle complet
◮ intégration a priori envisageable avec un oracle partiel et léger
Introduction
Automatisation de la génération de tests
◮ Prédicat de chemins
◮ Exécution symbolique
◮ Exécution concolique
◮ En pratique
◮ Discussion
◮ Complément : Aspects logiques
◮ Complément : Optimisations
Critères de test avancés
Au niveau solveurs :
chaines de caractères, flottants
Introduction
Automatisation de la génération de tests
◮ Prédicat de chemins
◮ Exécution symbolique
◮ Exécution concolique
◮ En pratique
◮ Discussion
◮ Complément : Aspects logiques
◮ Complément : Optimisations
Critères de test avancés
Remarques
compromis expressivité VS décidabilité / complexité
si théorie pas assez expressive : approximations [concrétisation]
Un fragment logique
on limite les connecteurs logiques
typiquement : pas de quantificateur, pas de ∨
Utilité : pratique pour relier des éléments entre eux de manière implicite
&x en C devient addr (X )
x une structure avec deux champs num et flag : num(X ) et flag (X )
Résolution très efficace
algorithme de congruence closure (cf Wikipedia)
polynomial
Variantes
(AC) axiomes d’associativité / commutativité sur certains symboles de
fonction
(free-algebra) algèbre de types libres
Compromis idéal ? ?
Observation 2004-2011 : théories de + en + puissantes
Deux technologies de solveurs
SMT : schéma très intéressant de combinaison de solveurs
(Nelson-Oppen) intégré à une gestion efficace des booléens
(plus confidentiel) Constraint Programming :
◮ pour les variables à domaines finis
◮ des approches intéressantes pour FLOAT, BV, (N≤ , +, ×)
Considérons l’instruction : x := a + b
Traduction 1 :
Xn+1 = An + Bn
Considérons l’instruction : x := a + b
Traduction 1 :
Xn+1 = An + Bn
Considérons l’instruction : x := a + b
Traduction 1 :
Xn+1 = An + Bn
Considérons l’instruction : x := a + b
Considérons l’instruction : x := a + b
Considérons l’instruction : x := a + b
Considérons l’instruction : x := a + b
Considérons l’instruction : x := a + b
Considérons l’instruction : x := a + b
Considérons l’instruction : x := a + b
Introduction
Automatisation de la génération de tests
◮ Prédicat de chemins
◮ Exécution symbolique
◮ Exécution concolique
◮ En pratique
◮ Discussion
◮ Complément : Aspects logiques
◮ Complément : Optimisations
Critères de test avancés
Variantes
~ ∧ Post(In,
Résumés de fonction de type Pre(In) ~ Out)
~
pas besoin d’entrer dans la fonction appelée pour énumérer les chemins
pas besoin d’entrer dans la fonction appelée pour générer des contraintes
Attention
résumés en sous-approximation
utilisation incrémentale de résumés en surapproximation
W
Résumés en sous-approximation de type ~ ⇒ ψ(out)
φ(in) ~
Couverture des chemins par DSE : tous les parcours de chemins se valent
Couverture des branches (budget limité ou non) : tous les parcours de chemins
ne se valent pas
DFS est souvent très mauvais
Quelques solutions
parcours hybride (Cute) : DFS + aléatoire
[simple, meilleur que dfs]
fitness guided [Exe, Sage, Pex] : les préfixes actifs sont évaluées, celui
de plus haut score est étendu
[mécanisme très versatile]
chemin actif : chemin non couvert, dont le plus long (strict) préfix est
couvert [on dit aussi préfix actif]
notion de score d’un chemin actif
à chaque étape : sélection + extension +
◮ choisir le chemin actif ayant le meilleur score
◮ “étendre” ce chemin : résolution + exécution
◮ ajouter les nouveaux chemins actifs créés par cette exécution
input : un programme P
output : RES : ensemble de couples (π - dt), tq pour chaque couple, P(dt)
couvre π et l’ensemble des dt couvre toutes les branches faisables de P
1: RES := ∅
2: v0 := get initial value /* arbitrary initial concrete value */
3: H := get new paths(v0 ) /* get all active prefixes from an execution */
4: While still uncovered branches or paths do
5: π := get best(H)
6: H := H\{π}
7: case Solve(Symb(π)) of
8: | UNSAT → nop
9: | SAT(v ) →
10: RES := RES ∪ {(π, v )} ;
11: H := H ∪ get new paths(v )
12: end case
13: end while
14: return RES
minCallDepth-dfs
les chemins dont le noeud final a la plus petite profondeur d’appel sont
prioritaires
puis dfs sur ces préfixes
hybrid dfs
alterner k1 test aléatoire puis k2 étapes de dfs à partir du dernier chemin
aléatoire
Best first
le prochain chemin est celui ayant le gain le plus élevé
gain, ex1 : nb de nouvelles instructions couvertes à coup sûr
gain, ex2 : nb d’instructions successeurs non encore couvertes
Enlever toutes les contraintes qui n’affectent pas le contrôle du chemin courant
ex : expressions de calcul du résultat final
à faire sur la formule, ou sur l’expression de chemin (plus simple)
Exemple de chemin :
y := y+1; x := a+b ; assume(y < 10) ; return x
prédicat de chemin : Y1 = Y0 + 1 ∧X1 = A0 + B0 ∧Y1 < 10
avec slicing : Y1 = Y0 + 1 ∧ Y1 < 10
propagation d’égalités
transformer X = A + B + 1 ∧ Y = A + B + 2 ∧ B ≤ Z en
let T :=A + B in X = T + 1 ∧ Y = T + 2 ∧ B ≤ Z
Remarques
~ ,V
Séparation de formules : Soit V ~1, V
~ 2 des ensembles de variables. Si ϕ(V
~)
~ ~ ~ ~
peut se décomposer en ϕ1 (V1 ) ∧ ϕ2 (V2 ) et V1 ∩ V2 = ∅, alors :
si Solve(ϕ1 ) retourne UNSAT alors UNSAT
sinon si Solve(ϕ2 ) retourne UNSAT alors UNSAT
sinon SAT, et une solution = solution(V~1 ) ∪ solution(V~2 )
* pour résoudre
X = Y + 3 ∧ X ≤ 5 ∧B ≥ 0 ∧B + 12 ≤ Z
on réutilise les anciennes valeurs de X , Y (6 et 8), et on résoud seulement
B ≥ 0 ∧B + 12 ≤ Z
Introduction
Automatisation de la génération de tests
Critères de test avancés
Critère mutationnel :
critère de test puissant en terme de détection de fautes
difficile à automatiser
Idée :
◮ Transformer les mutants (faibles) en prédicats/labels dans le
programme
Pourquoi :
◮ Bénéficier des avantages des deux familles de critères
◮ Automatiser efficacement la couverture de mutants
◮ Étendre l’exécution concolique à des critères de test avancés
Intérêt
critère de couverture le plus puissant du point de vue
théorique (peut émuler la plupart des autres)
bien corrélé en pratique à la découverte de bugs
Difficile à automatiser
calcul de couverture : M compilations, T × M exécutions
(rmq : M souvent très grand)
génération de TD : inexistant
labels :
- prédicats
- non exécutés
Programme avec label - no side-effects
statement i-1;
x := d; //d != f(d)
y := e; //e != g(e)
statement i+2;
Applications visées :
couverture
génération de tests
Méthode
utilisation d’outils en boı̂te noire :
◮ Emma : couverture
◮ PathCrawler : génération de tests
instrumentation du code pour “simuler” les labels
↓ ↓
Emma MuJava
programme programme
mutants
initial avec labels
124 LOC
TCas 0,03 s 0,03 s 5s
111 labels
436 LOC
Replace 0,05 s 0,10 s 40 s
607 labels
5400 LOC
Jtopas 3,22 s 11,72 s 1 400 s
610 labels