cours1 min de lecture

Mise au point des programmes

Bugs, débogage, jeux de tests et assertions — la démarche méthodique pour rendre un programme correct.
programme

Introduction

Un programme marche rarement du premier coup. Le travail de mise au point — détecter les erreurs, comprendre leur origine, les corriger, vérifier que le programme fait bien ce qu'il doit faire — occupe souvent plus de temps que l'écriture du code lui-même. Ce n'est pas un échec : c'est le cœur du métier.

Un bon programmeur ressemble moins à un magicien qu'à un détective : il collecte des indices (messages d'erreur, valeurs intermédiaires), formule des hypothèses, les confronte aux faits, et resserre son enquête jusqu'au coupable. L'enjeu de ce cours est de transformer la mise au point en démarche méthodique plutôt qu'en errance hasardeuse devant un message d'erreur incompréhensible.

Les trois grands types d'erreurs

TypeSymptômeExemple
SyntaxePython refuse même de lancer le programme.Oubli des :, parenthèse non fermée.
ExécutionLe programme démarre puis plante avec une exception.Division par zéro, indice hors liste.
LogiqueLe programme tourne, mais le résultat est faux.Le calcul de moyenne renvoie un mauvais nombre.

Les erreurs logiques sont les plus dangereuses : sans test, elles peuvent passer inaperçues.

Erreur de syntaxe

if x > 0
    print("positif")

Sortie :

File "...", line 1
    if x > 0
           ^
SyntaxError: expected ':'

Le : manquant suffit à empêcher l'exécution.

Erreur d'exécution (exception)

notes = [12, 15, 14]
print(notes[10])

Sortie :

IndexError: list index out of range

Python a tenté l'opération et a abandonné en chemin.

Erreur de logique

def moyenne(notes):
    total = 0
    for n in notes:
        total + n           # oubli du = → ne modifie pas total
    return total / len(notes)

print(moyenne([10, 12, 14]))   # 0.0 — pas l'erreur attendue !

Le programme tourne sans crier, mais répond n'importe quoi.

Quel est le type d'erreur le plus difficile à détecter ?

Lire un message d'erreur

Quand Python plante, il affiche une trace (traceback) qui se lit de haut en bas pour suivre l'enchaînement et de bas en haut pour trouver la cause immédiate.

Traceback (most recent call last):
  File "calculs.py", line 12, in <module>
    print(moyenne([]))
  File "calculs.py", line 8, in moyenne
    return total / len(notes)
ZeroDivisionError: division by zero

Trois informations clés :

  1. Le type d'erreur : ZeroDivisionError.
  2. Le message : division by zero.
  3. L'endroit : ligne 8 dans la fonction moyenne.

Ici, l'enchaînement révèle qu'on appelle moyenne([]) — liste vide, len([]) == 0, division par zéro. La cause est dans l'appel, pas dans la fonction.

Toujours commencer par lire le message d'erreur avant de modifier le code. La grande majorité des erreurs sont résolues en 30 secondes par une lecture attentive du Traceback.

Le print de débogage

L'outil de débogage le plus universel : afficher l'état des variables à des points clés du programme.

def moyenne(notes):
    total = 0
    for n in notes:
        print("avant :", total, "ajout :", n)   # debug
        total += n
        print("après :", total)                  # debug
    return total / len(notes)

print(moyenne([10, 12, 14]))

Sortie :

avant : 0 ajout : 10
après : 10
avant : 10 ajout : 12
après : 22
avant : 22 ajout : 14
après : 36
12.0

On voit en direct comment total évolue, et on confirme que la valeur finale est cohérente. Une fois le bug corrigé, on retire les print.

Jeux de tests

Pour qu'un programme soit correct, il ne suffit pas qu'il marche sur un exemple : il faut qu'il marche sur un échantillon représentatif d'entrées possibles. C'est un jeu de tests.

Quatre familles de cas à toujours envisager :

FamilleExemples pour moyenne(notes)
Cas normaux[10, 12, 14], [8, 15]
Cas limites[20, 20, 20] (toutes à 20), [0] (un seul élément)
Cas extrêmesliste de 100 notes, notes très proches de 0 ou 20
Cas interdits[] (vide), [25] (hors plage) → précondition
Le succès d'un jeu de tests ne prouve pas la correction. Il prouve que le programme marche sur ces cas. Il peut très bien échouer sur le 51ᵉ cas qu'on n'a pas pensé à tester.C'est exactement le commentaire associé à l'item du programme : « le succès d'un jeu de tests ne garantit pas la correction d'un programme ».

Tester avec assert

Le moyen le plus simple de coder un jeu de tests : enchaîner des assertions qui doivent toutes passer.

def double(n):
    return n * 2

# Jeu de tests
assert double(0) == 0
assert double(3) == 6
assert double(-2) == -4
assert double(100) == 200

print("Tous les tests passent.")

Si une assertion échoue, Python lève AssertionError à la ligne précise. Si toutes passent, on voit le message final.

⏵ Ctrl+↵ pour exécuter
Aucune exécution pour l'instant.

Sortie attendue :

Tous les tests passent.

Démarche méthodique

Devant un bug, suivre la séquence :

  1. Reproduire le bug — quel appel précis le déclenche ?
  2. Isoler : trouver l'entrée la plus simple qui plante.
  3. Lire le message d'erreur si présent.
  4. Tracer : print ou débogueur pour suivre les valeurs.
  5. Formuler une hypothèse sur la cause.
  6. Corriger, puis relancer le jeu de tests complet — pas seulement le cas qui plantait.

La dernière étape est cruciale : une correction peut introduire un nouveau bug. C'est la régression. Le jeu de tests complet protège contre cela.

Que se passe-t-il si assert 1 == 2 est exécuté ?

Pièges courants

  • Modifier au hasard : changer une ligne en espérant que ça marche sans comprendre est la pire stratégie.
  • Tester uniquement les cas normaux : un programme correct gère aussi les cas limites (liste vide, valeur nulle…).
  • Oublier de relancer tous les tests après correction — risque de régression.
  • Confondre absence d'erreur et correction : un programme qui ne plante pas peut tout à fait donner de mauvais résultats.
  • Laisser les print de debug dans le code final.

Pour aller plus loin

Le jeu de tests est tellement central qu'il a donné naissance à un style de programmation entier : le TDD (Test-Driven Development) — on écrit les tests avant le code. Vous le pratiquerez avec naturel dès que la spécification sera bien en place : précondition → postcondition → tests → code.