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 incorrect
- Le programme ne fait pas ce que le programmeur souhaitait 
- Rappel : le programme fait ce que le programmeur lui a demandé! 
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 avec- cout, ou mieux- cerr.
- 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")
Avantages
- Très simple à mettre en œuvre 
Inconvénients
- Modification du programme, recompilation et réexécution chaque fois que l’on souhaite changer ce que l’on observe 
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 - cerrplutôt que- cout(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).
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.
Inconvénients
- Un peu lourd à mettre en œuvre 
- Pas encore intégrée dans Jupyter pour C++ 
- Pas encore disponible sur myDocker 
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? 
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
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 
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 recette
- Garantir la robustesse d’une brique avant d’empiler dessus 
 Tests unitaires
- Anticiper la mise en commun de plusieurs briques 
 Tests d’intégration
- Anticiper la mise en production 
 Tests de charge
- Alerte 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é