---
jupytext:
text_representation:
extension: .md
format_name: myst
format_version: 0.13
kernelspec:
display_name: C++17
language: C++17
name: xcpp17
rise:
auto_select: first
autolaunch: false
centered: false
controls: false
enable_chalkboard: true
height: 100%
margin: 0
maxScale: 1
minScale: 1
scroll: true
slideNumber: true
start_slideshow_at: selected
transition: none
width: 90%
---
+++ {"slideshow": {"slide_type": "slide"}}
# Débogage et Tests
+++ {"slideshow": {"slide_type": "fragment"}}
## Prélude
+++
- Exercices PLaTon: directement dans Jupyter!
+++ {"slideshow": {"slide_type": "slide"}}
### 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?
+++ {"slideshow": {"slide_type": "fragment"}}
**
Passage à l’échelle!
**
+++ {"slideshow": {"slide_type": "fragment"}}
**Écrire des programmes corrects
**
+++ {"slideshow": {"slide_type": "slide"}}
## Corriger les erreurs : le débogage
+++ {"slideshow": {"slide_type": "slide"}}
### Exemple
Le bout de code suivant contient plusieurs erreurs.
Comment pourrait-on s'y prendre pour les trouver?
```{code-cell}
---
slideshow:
slide_type: fragment
---
using namespace std;
#include
```
```{code-cell}
---
slideshow:
slide_type: fragment
---
/** 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 resultat = true;
for ( int i = 0; i < n/2; i++ ) {
if ( mot[i] != mot[n-i] ) {
resultat = false;
} else {
resultat = true; }
}
return true;
}
```
```{code-cell}
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"));
```
```{code-cell}
```
```{code-cell}
```
+++ {"slideshow": {"slide_type": "fragment"}}
:::{admonition} Erreurs trouvées
- Erreur de syntaxe
- Erreurs de sémantique
:::
+++ {"slideshow": {"slide_type": "fragment"}}
:::{admonition} 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
:::
+++ {"slideshow": {"slide_type": "slide"}}
### Débogage, selon le type d’erreur
+++ {"slideshow": {"slide_type": "fragment"}}
#### Erreurs de syntaxe
+++ {"slideshow": {"slide_type": "fragment"}}
**Symptômes :**
Détectées par l'interpréteur ou le compilateur
+++ {"slideshow": {"slide_type": "fragment"}}
**Débogage :**
1. Bien lire les messages d’erreurs
2. ⚠️ Le compilateur pointe vers là où il détecte l’erreur⚠️
Pas forcément là où est l’erreur
+++ {"slideshow": {"slide_type": "slide"}}
#### Erreurs à l’exécution
+++ {"slideshow": {"slide_type": "fragment"}}
**Exemple :** exemple-segmentation-fault.cpp
+++ {"slideshow": {"slide_type": "fragment"}}
**Symptômes :**
- Segmentation fault!
- Exceptions : division par zéro, ...
+++ {"slideshow": {"slide_type": "fragment"}}
**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 :
```
g++ -g -fsanitize=address exemple-segmentation-fault.cpp -o exemple-segmentation-fault
```
- En C++ : Utilisation du débogeur!
Voir plus loin.
+++ {"slideshow": {"slide_type": "slide"}}
### 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é!
+++ {"slideshow": {"slide_type": "fragment"}}
:::{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
:::
+++ {"slideshow": {"slide_type": "fragment"}}
:::{admonition} 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 ...
:::
+++ {"slideshow": {"slide_type": "slide"}}
## Stratégies de débogage
+++ {"slideshow": {"slide_type": "slide"}}
### Stratégie : tracer l'exécution du programme
**Étapes :**
1. **Instrumenter** le programme
À notre niveau : insérer des affichages avec `cout` ou `cerr`
2. Exécuter le programme
3. Analyser le **journal d'exécution** (log)
À notre niveau : regarder ce qui est affiché
+++ {"slideshow": {"slide_type": "slide"}}
**Exemple :**
```{code-cell}
---
slideshow:
slide_type: fragment
---
using namespace std;
#include
```
```{code-cell}
---
slideshow:
slide_type: fragment
---
bool estPalindrome(string mot) {
int n = mot.size();
bool result = true;
for ( int i = 0; i < n/2; i++ ) {
// Instrumentation du code
cout << i << " ";
cout << result << " ";
cout << mot[i] << " ";
cout << mot[n-1-i] << endl;
//
if ( mot[i] != mot[n-1-i] ) {
result = false;
} else {
result = true;}
}
return result;
}
```
```{code-cell}
---
slideshow:
slide_type: fragment
---
estPalindrome("abcddcbz")
```
+++ {"slideshow": {"slide_type": "fragment"}}
:::{hint} Avantages
- Simple à mettre en œuvre
:::
+++ {"slideshow": {"slide_type": "fragment"}}
:::{attention} Inconvénients
- Modification du programme, recompilation et réexécution
chaque fois que l'on souhaite changer ce que l'on observe
:::
+++ {"slideshow": {"slide_type": "slide"}}
### Stratégie : utiliser le débogueur
+++ {"slideshow": {"slide_type": "fragment"}}
#### Exemple : analyse post-mortem d'une erreur à l'exécution ♣
Dans un terminal, on compile le programme (noter l'option `-g`) :
```
$ clang++ -g exemple-segmentation-fault.cpp -o exemple-segmentation-fault
```
+++ {"slideshow": {"slide_type": "fragment"}}
Quand on l'exécute, le programme plante :
```
$ ./exemple-segmentation-fault
segmentation fault (core dumped) ./exemple-segmentation-fault
```
+++ {"slideshow": {"slide_type": "fragment"}}
«core dumped» : une copie de la mémoire du programme au moment du
plantage (*core*) a été enregistrée dans un fichier (*dumped*).
+++ {"slideshow": {"slide_type": "fragment"}}
:::{attention} Complication technique
Selon la configuration du système, le fichier peut s'appeler :
- `core`
- `core.`
- `/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
```
:::
+++ {"slideshow": {"slide_type": "fragment"}}
Utilisons le débogueur pour analyser cet état *post-mortem* (après plantage) :
```
$ gdb --tui --silent exemple-segmentation-fault
(gdb) core core
...
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
```
+++ {"slideshow": {"slide_type": "slide"}}
#### Exemple : exécution pas à pas
Dans un terminal, on compile le programme (noter l'option `-g`) :
``` bash
$ clang++ -g est-palindrome.cpp -o est-palindrome
```
+++ {"slideshow": {"slide_type": "fragment"}}
Quand on l'exécute, le résultat est incorrect :
``` bash
$ ./est-palindrome
abcddcbz est un palindrome
```
+++ {"slideshow": {"slide_type": "fragment"}}
Lançons le débogueur pour **observer l'exécution du programme :**
```
$ gdb --tui --silent est-palidrome
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 result = 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 result = 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 result = true;}
```
+++ {"slideshow": {"slide_type": "slide"}}
#### Résumé
Un outil très puissant : le débogueur pas à pas
:::{admonition} Usages
- Analyse post-mortem après un plantage du programme
- Observation du programme en cours de fonctionnement
:::
+++ {"slideshow": {"slide_type": "fragment"}}
:::{admonition} 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.
:::
+++ {"slideshow": {"slide_type": "fragment"}}
:::{attention} Inconvénients
- Un peu lourd à mettre en œuvre
- Pas encore intégrée dans Jupyter pour C++
- Seule l'analyse post-mortem est fonctionnelle sur le service JupyterHub
:::
+++ {"slideshow": {"slide_type": "fragment"}}
:::{hint} 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
:::
+++ {"slideshow": {"slide_type": "fragment"}}
#### 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
+++ {"slideshow": {"slide_type": "slide"}}
### Stratégie : réduire le problème par dichotomie
1. Caractériser le bogue : «lorsque j’appelle telle fonction avec tels
paramètres, la réponse est incorrecte»
En faire un test!
2. Faire une expérience pour déterminer dans quelle «moitié» du
programme est l’erreur.
3. Trouver le plus petit exemple incorrect
En faire un test!
4. Exécuter pas à pas la fonction sur cet exemple
5. Trouver l’erreur
6. Corriger l’erreur
7. Vérifier les tests (non régression)
8. Rajouter des tests?
+++ {"slideshow": {"slide_type": "fragment"}}
:::{hint} Astuce
Être efficace dans la boucle essai-erreur!!!
:::
+++ {"slideshow": {"slide_type": "slide"}}
### Gagner du temps : développement piloté par les tests
:::{hint} 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
:::
+++ {"slideshow": {"slide_type": "fragment"}}
:::{hint} 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
:::
+++ {"slideshow": {"slide_type": "slide"}}
## Tests : pour aller plus loin ♣
+++ {"slideshow": {"slide_type": "slide"}}
### 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
+++ {"slideshow": {"slide_type": "slide"}}
### Méthodes agiles 2000- ♣
Exemple :
:::{image} media/XP-feedback.png
:alt: image programmation agile
:width: 30%
:::
+++ {"slideshow": {"slide_type": "fragment"}}
:::{admonition} Objectif : remettre le développeur au coeur du processus
- Créativité
- Responsabilité
- Auto assurance : **Tests!**
:::
+++ {"slideshow": {"slide_type": "fragment"}}
**Les tests sont votre outil de libération!**
+++ {"slideshow": {"slide_type": "slide"}}
### 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
+++ {"slideshow": {"slide_type": "slide"}}
### Il faut aller plus loin! ♣
- Article [«infecté par les tests»](http://junit.sourceforge.net/doc/testinfected/testing.htm)
- [Wikipedia : Introduction au test logiciel](http://fr.wikibooks.org/wiki/Introduction_au_test_logiciel)
- Tests unitaires en Java : [JUnit](http://junit.org/)
- Tests unitaires en Python : pytest
- Tests unitaires en C++ :
[cppunit](http://cppunit.sourceforge.net/doc/cvs/cppunit_cookbook.html),
[doctest](https://github.com/doctest/doctest)
+++ {"slideshow": {"slide_type": "slide"}}
## Résumé
+++ {"slideshow": {"slide_type": "fragment"}}
### Types d'erreur
- Erreurs de syntaxe
- Erreurs à l'exécution
- Erreurs sémantiques
+++ {"slideshow": {"slide_type": "fragment"}}
### 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
+++ {"slideshow": {"slide_type": "fragment"}}
### 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
+++ {"slideshow": {"slide_type": "fragment"}}
### Tests automatique
- Pour être efficace
- Au S1 : ` CHECK( f( ... ) == ... ); `
- Au S2 : une bibliothèque de tests unitaires plus riche
```{code-cell}
#include
using namespace std;
cout << "machin" << 1+3 << endl;
```
```{code-cell}
:tags: []
#include
using namespace std;
```
```{code-cell}
:tags: []
ostringstream cout;
```
```{code-cell}
:tags: []
cout << "machin" << 1 + 3 << endl;
```
```{code-cell}
:tags: []
cout.str()
```
```{code-cell}
:tags: []
int r = 1
```
```{code-cell}
:tags: []
1 + 1
```
```{code-cell}
:tags: []
1 + 1 ;
```
```{code-cell}
:tags: []
r = 2
```
```{code-cell}
:tags: []
CHECK( 1 == 1 )
CHECK( 2 == 2 )
```
```{code-cell}
:tags: []
1 + 1
2 + 2
```
```{code-cell}
```