Débogage et Tests#
Prélude#
Annonces#
Examen mi semestre : corrigé!
copie par mail envoyée jeudi soir (tard)
notes temporaires sur eCampus: à venir
Projet : les sujets sont disponibles au téléchargement (via le tableau de bord)
TD : pas de TD cette semaine; dernier TD la semaine prochaine
Exercices Jupylates: nouveaux exercices à venir
Résumé des épisodes précédents …#
Pour le moment nous avons vu les concepts suivants :
Contrôle du flot d’exécution : instructions conditionnelles et itératives, fonctions
Gestion des données : variables, tableaux, collections, entrées et sorties, fichiers
Méthodologie de développement : fonctions, documentation, test
Pourquoi aller plus loin?
Corriger les erreurs : le débogage#
Exemple#
Le bout de code suivant contient plusieurs erreurs. Comment pourrait-on s’y prendre pour les trouver?
using namespace std;
#include <iostream>
/** Teste si mot est un palindrome
* @param mot une chaîne de charactères
* @result un booléen
**/
bool estPalindrome(string mot) {
int n = mot.size()
bool résultat = true;
for ( int i = 0; i < n/2; i++ ) {
if ( mot[i] != mot[n-i] ) {
résultat = false;
} else {
résultat = true; }
}
return résultat;
}
CHECK( estPalindrome("kayak") )
CHECK( not estPalindrome("bonjour") )
CHECK( estPalindrome("aa") )
CHECK( estPalindrome("aaaaa") )
CHECK( estPalindrome("") )
CHECK( estPalindrome("abba") )
CHECK( estPalindrome("aba") );
CHECK( not estPalindrome("abby"));
Erreurs trouvées
Erreur de syntaxe
Erreurs de sémantique
Stratégies mises en œuvre
Compiler le code
Exécuter le code et regarder les messages d’erreurs
Faire des tests
Lire en détail le code
Tracer l’exécution du code
Débogage, selon le type d’erreur#
Erreurs de syntaxe#
Symptômes :
Détectées par l’interpréteur ou le compilateur
Débogage :
Bien lire les messages d’erreurs
⚠️ Le compilateur pointe vers là où il détecte l’erreur⚠️
Pas forcément là où est l’erreur
Erreurs à l’exécution#
Exemple : exemple-segmentation-fault.cpp
Symptômes :
Segmentation fault!
Exceptions : division par zéro, …
Débogage :
Analyser l’état du programme au moment de l’erreur
En Python, Java, … : regarder la pile d’appel («strack trace»)
En C++ aussi avec la bonne option de compilation :clang++ -g -fsanitize=address exemple-segmentation-fault.cpp -o exemple-segmentation-fault
En C++ : Utilisation du débogeur!
Voir plus loin.
Erreurs sémantiques#
Symptômes :
Le programme s’exécute «normalement»
mais le résultat est incorrectLe programme ne fait pas ce que le programmeur souhaitait
Rappel : le programme fait ce que le programmeur lui a demandé!
Danger
Difficulté
Isoler une erreur glissée dans :
Un programme de millions de lignes
De grosses données
Des milliards d’instructions exécutées
Un travail de détective!
Peut être très frustrant, surtout sous stress
Peut être une très belle source de satisfaction
Requiert de gérer ses émotions, et celle de son équipe …
Stratégies de débogage#
Stratégie : tracer l’exécution du programme#
Étapes :
Instrumenter le programme
À notre niveau : insérer des affichages aveccout
, ou mieuxcerr
.Exécuter le programme
Analyser le journal d’exécution (log)
À notre niveau : regarder ce qui est affiché
Exemple :
using namespace std;
#include <iostream>
bool estPalindrome(string mot) {
int n = mot.size();
bool résultat = true;
for ( int i = 0; i < n/2; i++ ) {
// Instrumentation du code
cerr << i << " ";
cerr << résultat << " ";
cerr << mot[i] << " ";
cerr << mot[n-1-i] << endl;
//
if ( mot[i] != mot[n-1-i] ) {
résultat = false;
} else {
résultat = true;}
}
return résultat;
}
estPalindrome("abcddcbz")
Indication
Avantages
Très simple à mettre en œuvre
Attention
Inconvénients
Modification du programme, recompilation et réexécution chaque fois que l’on souhaite changer ce que l’on observe
Attention
Attention Pour garantir que les affichages soient bien tous visibles, même en cas d’erreur à l’exécution, assurez-vous comme si dessus :
d’utiliser
cerr
plutôt quecout
(pour ne pas avoir de tampon);d’afficher un saut de ligne (pour que l’affichage ne soit pas recouvert par l’invite de commande suivante).
Stratégie : analyse post-mortem de la pile d’appel lors d’une erreur à l’exécution#
Dans un terminal, on compile le programme :
clang++ exemple-segmentation-fault.cpp -o exemple-segmentation-fault
Quand on l’exécute, le programme plante :
./exemple-segmentation-fault
segmentation fault (core dumped) ./exemple-segmentation-fault
Tel quel, ce n’est pas très informatif.
Recompilons avec les option -g
et -fsanitize=address
:
clang++ -g -fsanitize=address exemple-segmentation-fault.cpp -o exemple-segmentation-fault
./exemple-segmentation-fault
...
#0 0x5d59518f6281 in main /home/nthiery/Enseignement/Info111/Semaine9/exemple-segmentation-fault.cpp:7
...
...
Maintenant, la pile d’appel est affichée.
Nous utilisons une version de clang++
un peu datée; si les numéros
de lignes n’apparaissent pas dans la pile d’appel, on peut essayer
avec le compilateur g++
qui donne de meilleurs résultats :
g++ -g -fsanitize=address exemple-segmentation-fault.cpp -o exemple-segmentation-fault
./exemple-segmentation-fault
Stratégie : utiliser le débogueur#
Exemple : analyse post-mortem d’une erreur à l’exécution ♣#
Recompilons le programme (noter l’option -g
) :
clang++ -g exemple-segmentation-fault.cpp -o exemple-segmentation-fault
Quand on l’exécute, le programme plante :
./exemple-segmentation-fault
segmentation fault (core dumped) ./exemple-segmentation-fault
«core dumped» : une copie de la mémoire du programme au moment du plantage (core) a été enregistrée dans un fichier (dumped).
Attention
Complication technique
Selon la configuration du système, le fichier peut s’appeler :
core
core.<numéro du processus>
/var/lib/apport/coredump/core...exemple-segmentation-fault...
/var/lib/systemd/coredump/core.exemple-segmentation-fault...
… ou ne pas être enregistré du tout du fait de limites système :
ulimit -c
ulimit -c unlimited
Utilisons le débogueur pour analyser cet état post-mortem (après plantage) :
coredumpctl debug
...
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000055ff8b3aa1b3 in main () at exemple-segmentation-fault.cpp:7
7 v[i] = 1;
(gdb) p i
$1 = 10
Exemple : exécution pas à pas#
Dans un terminal, on compile le programme (noter l’option -g
) :
clang++ -g est-palindrome.cpp -o est-palindrome
Quand on l’exécute, le résultat est incorrect :
$ ./est-palindrome
abcddcbz est un palindrome
Lançons le débogueur pour observer l’exécution du programme :
gdb --tui --silent est-palindrome
GNU gdb (Ubuntu 8.3-0ubuntu1) 8.3
(gdb) b 13
Breakpoint 1 at 0x1250: file est-palindrome.cpp, line 13.
(gdb) start
Temporary breakpoint 2, main () at est-palindrome.cpp:25
25 string s = "abcddcbz";
(gdb) c
Continuing.
Breakpoint 1, estPalindrome (mot=...) at est-palindrome.cpp:13
13 int n = mot.size();
(gdb) n
14 bool résultat = true;
(gdb) n
15 for ( int i = 0; i < n/2; i++ ) {
(gdb) n
16 if ( mot[i] != mot[n-i-1] ) {
(gdb) p i
$1 = 0
(gdb) p mot[i]
$2 = ... 'a'
(gdb) p mot[n-i-1]
$3 = ... 'z'
(gdb) n
17 résultat = false;
(gdb) n
15 for ( int i = 0; i < n/2; i++ ) {
(gdb) n
16 if ( mot[i] != mot[n-i-1] ) {
(gdb) p mot[i]
$4 = 'b'
(gdb) p mot[n-i-1]
$5 = 'b'
(gdb) n
19 résultat = true;}
Résumé#
Un outil très puissant : le débogueur pas à pas
Usages
Analyse post-mortem après un plantage du programme
Observation du programme en cours de fonctionnement
Outils
En C++ :
gdb
En Python : module
pdb
Les Environnements de Développement Intégrés (
Code::Blocks
, …) fournissent des interfaces graphiques confortables pour utiliser le déboggueur.
Attention
Inconvénients
Un peu lourd à mettre en œuvre
Pas encore intégrée dans Jupyter pour C++
Pas encore disponible sur myDocker
Indication
Avantages
Contrôle fin de l’exécution :
En passant à la ligne suivante (next)
En rentrant dans les sous fonctions (step)
Jusqu’au prochain points d’arrêts (conditionnels)
Contrôle fin de l’affichage
Suivi des variables
Analyse de la pile d’exécution
Analyse de l’état de la mémoire
Pas besoin de recompiler
Mode d’emploi#
Compiler avec l’option
-g
:clang++ -g monprogramme.cpp -o monprogramme
Lancer le débogueur avec :
gdb --tui --silent monprogramme
(
--tui
: text user interface;--silent
: ne pas afficher copyright et message d’introduction au démarrage)Commandes du débogueur (raccourcis :
s
,n
,c
,b l
,p expr
,q
, …)start // lance le programme en pas à pas next // instruction suivante step // instruction suivante; entre dans les fonctions continue // continue jusqu'au point d'arrêt suivant break l // ajoute un point d'arrêt ligne `l` print expr // affiche la valeur de l'expression refresh // rafraîchit l'affichage quit // quitte
Commandes plus avancées ♣
display expr // affiche la valeur de l'expression (persistant) continue // continue l'exécution du programme show locals // affiche les variables locales where full // affiche la pile d'appel help // aide en ligne
Stratégie : réduire le problème par dichotomie#
Caractériser le bogue : «lorsque j’appelle telle fonction avec tels paramètres, la réponse est incorrecte»
En faire un test!Faire une expérience pour déterminer dans quelle «moitié» du programme est l’erreur.
Trouver le plus petit exemple incorrect
En faire un test!Exécuter pas à pas la fonction sur cet exemple
Trouver l’erreur
Corriger l’erreur
Vérifier les tests (non régression)
Rajouter des tests?
Indication
Astuce Être efficace dans la boucle essai-erreur!!!
Gagner du temps : développement piloté par les tests#
http://fr.wikipedia.org/wiki/Test_Driven_Development
Indication
Bonne pratique durant le développement Pour ajouter une nouvelle fonctionnalité :
Écrire les spécifications (typiquement sous forme de javadoc!)
Écrire le test correspondant
Attention aux cas particuliers!
Le développement est terminé lorsque les tests passent
Indication
Bonne pratique durant le débogage Pour corriger un bogue signalé :
Écrire un test qui met en évidence le bogue
Le débogage est terminé quand les tests passent
Tests : pour aller plus loin ♣#
Approche qualité traditionnelle ♣#
Équipes très hiérarchisées :
Analystes programmeurs
Équipe de test
Développeur
Procédures strictes (voire lourdes)
Évolution lente, planifiée longuement à l’avance
Méthodes agiles 2000- ♣#
Exemple : http://fr.wikipedia.org/wiki/Extreme_programming
Objectif : remettre le développeur au coeur du processus
Créativité
Responsabilité
Auto assurance : Tests!
Les tests sont votre outil de libération!
Les objectifs et types de tests ♣#
Mesurer la qualité du code
Mesurer les progrès
Formaliser (et anticiper!) les demandes du client
Tests fonctionnels
Cahier de recetteGarantir la robustesse d’une brique avant d’empiler dessus
Tests unitairesAnticiper la mise en commun de plusieurs briques
Tests d’intégrationAnticiper la mise en production
Tests de chargeAlerte immédiate si l’on introduit un bogue
Tests de non régression
Il faut aller plus loin! ♣#
Article «infecté par les tests»
Tests unitaires en Java : JUnit
Tests unitaires en Python : pytest
Résumé#
Types d’erreur#
Erreurs de syntaxe
Erreurs à l’exécution
Erreurs sémantiques
Stratégies de déboggage#
Prévention : dévelopement incrémental
ne jamais trop s’éloigner d’une version qui marche
Réduire le problème par dichotomie
Analyser les plantages
pile d’appel
débogueur
Observer l’exécution :
traçage
débogueur pas à pas
Tests#
Pour du code robuste
Pour mesurer la qualité (suivre les progrès et régressions)
Pour éviter ou simplifier le débogage
Pour être serein
Tests automatique#
Pour être efficace
Au S1 :
CHECK( f( ... ) == ... );
Au S2 : une bibliothèque de tests unitaires plus riche
Comment vous sentez-vous en ce début de cours?#
Curieux
En colère
Inquiet
Fatigué