Partie 2 : Spécification et tests
1. Spécifier une fonction
Section intitulée « 1. Spécifier une fonction »1.1 Pourquoi spécifier ?
Section intitulée « 1.1 Pourquoi spécifier ? »Écrire un programme juste est plus important qu'écrire un programme qui « fonctionne ». La spécification d'une fonction décrit précisément ce qu'elle fait, avant même de l'implémenter.
Une bonne spécification répond à trois questions :
- Quoi ? - Que fait la fonction ? (description)
- Avec quoi ? - Quels sont les paramètres et leurs types ? (préconditions)
- Pour quoi ? - Que renvoie-t-elle ? (postconditions)
1.2 Le prototype
Section intitulée « 1.2 Le prototype »Le prototype d'une fonction décrit son nom, ses paramètres et leur type, ainsi que le type de la valeur renvoyée.
def moyenne(notes: list) -> float:En Python, les annotations de type (: list, -> float) sont facultatives mais très utiles pour documenter le code.
1.3 La docstring
Section intitulée « 1.3 La docstring »La docstring est une chaîne de caractères placée juste après la ligne def, qui décrit le comportement de la fonction :
def moyenne(notes: list) -> float: """Calcule la moyenne d'une liste de nombres.
Preconditions: notes est une liste non vide de nombres (int ou float). Postconditions: Renvoie la moyenne arithmétique des éléments de notes. """ return sum(notes) / len(notes)1.4 Préconditions et postconditions
Section intitulée « 1.4 Préconditions et postconditions »| Terme | Définition | Exemple |
|---|---|---|
| Précondition | Condition que les arguments doivent vérifier pour que la fonction fonctionne correctement | notes est non vide, contient des nombres |
| Postcondition | Propriété que le résultat doit vérifier si les préconditions sont respectées | Le résultat est compris entre min(notes) et max(notes) |
Les préconditions et postconditions forment un contrat entre l'utilisateur de la fonction et son auteur :
- L'utilisateur s'engage à respecter les préconditions
- L'auteur garantit que les postconditions seront satisfaites
1.5 Exemple complet de spécification
Section intitulée « 1.5 Exemple complet de spécification »def recherche(tableau: list, valeur) -> int: """Recherche la première occurrence de valeur dans tableau.
Preconditions: tableau est une liste (éventuellement vide). valeur est du même type que les éléments de tableau. Postconditions: Renvoie l'indice de la première occurrence de valeur dans tableau. Renvoie -1 si valeur n'est pas dans tableau. """ for i in range(len(tableau)): if tableau[i] == valeur: return i return -12. Les assertions
Section intitulée « 2. Les assertions »2.1 Qu'est-ce qu'une assertion ?
Section intitulée « 2.1 Qu'est-ce qu'une assertion ? »Une assertion est une instruction qui vérifie qu'une condition est vraie. Si la condition est fausse, le programme s'arrête immédiatement avec une erreur AssertionError.
assert 2 + 2 == 4 # passe (la condition est vraie)assert 2 + 2 == 5 # ERREUR : AssertionErrorOn peut ajouter un message d'erreur :
assert len(notes) > 0, "La liste de notes ne doit pas être vide"2.2 Vérifier les préconditions avec assert
Section intitulée « 2.2 Vérifier les préconditions avec assert »Les assertions sont utiles pour vérifier les préconditions au début d'une fonction :
def moyenne(notes: list) -> float: """Calcule la moyenne d'une liste de nombres.""" assert isinstance(notes, list), "notes doit être une liste" assert len(notes) > 0, "La liste ne doit pas être vide" assert all(isinstance(n, (int, float)) for n in notes), "Tous les éléments doivent être des nombres" return sum(notes) / len(notes)>>> moyenne([12, 15, 8, 14])12.25>>> moyenne([])AssertionError: La liste ne doit pas être vide>>> moyenne("texte")AssertionError: notes doit être une liste2.3 Vérifier les postconditions
Section intitulée « 2.3 Vérifier les postconditions »On peut aussi vérifier les postconditions :
def division_entiere(a: int, b: int) -> tuple: """Renvoie (quotient, reste) de la division de a par b.
Preconditions: a >= 0, b > 0, a et b entiers. Postconditions: a == quotient * b + reste et 0 <= reste < b. """ assert isinstance(a, int) and isinstance(b, int), "a et b doivent être entiers" assert a >= 0, "a doit être positif ou nul" assert b > 0, "b doit être strictement positif"
q = a // b r = a % b
# Vérification de la postcondition assert a == q * b + r, "Postcondition violée" assert 0 <= r < b, "Le reste doit être entre 0 et b-1"
return (q, r)3. Les jeux de tests
Section intitulée « 3. Les jeux de tests »3.1 Pourquoi tester ?
Section intitulée « 3.1 Pourquoi tester ? »Comme le disait Edsger Dijkstra : « Tester un programme démontre la présence de bugs, pas leur absence. »
Malgré cette limite, les tests sont indispensables pour :
- Vérifier que la fonction produit les résultats attendus
- Détecter les régressions lors de modifications
- Documenter le comportement attendu
- Réfléchir aux cas limites
3.2 Écrire des tests avec assert
Section intitulée « 3.2 Écrire des tests avec assert »La méthode la plus simple consiste à écrire des assertions qui comparent le résultat de la fonction avec le résultat attendu :
def valeur_absolue(x): """Renvoie la valeur absolue de x.""" if x >= 0: return x else: return -x
# Jeu de testsassert valeur_absolue(5) == 5, "Nombre positif"assert valeur_absolue(-3) == 3, "Nombre négatif"assert valeur_absolue(0) == 0, "Zéro"assert valeur_absolue(-0.5) == 0.5, "Flottant négatif"print("Tous les tests passent !")3.3 Choisir les cas de test
Section intitulée « 3.3 Choisir les cas de test »Un bon jeu de tests doit couvrir :
| Type de cas | Description | Exemple pour valeur_absolue |
|---|---|---|
| Cas normal | Entrée courante | valeur_absolue(5) → 5 |
| Cas limite | Valeur aux frontières | valeur_absolue(0) → 0 |
| Cas extrême | Grandes ou petites valeurs | valeur_absolue(-1000000) → 1000000 |
| Cas d'erreur | Entrée invalide | valeur_absolue("texte") → erreur |
3.4 Exemple : tester une fonction de tri
Section intitulée « 3.4 Exemple : tester une fonction de tri »def tri_selection(tableau): """Trie un tableau en place par sélection.""" for i in range(len(tableau)): i_min = i for j in range(i + 1, len(tableau)): if tableau[j] < tableau[i_min]: i_min = j tableau[i], tableau[i_min] = tableau[i_min], tableau[i]
# Testst1 = [3, 1, 4, 1, 5]tri_selection(t1)assert t1 == [1, 1, 3, 4, 5], "Cas normal"
t2 = []tri_selection(t2)assert t2 == [], "Tableau vide"
t3 = [42]tri_selection(t3)assert t3 == [42], "Un seul élément"
t4 = [1, 2, 3, 4, 5]tri_selection(t4)assert t4 == [1, 2, 3, 4, 5], "Déjà trié"
t5 = [5, 4, 3, 2, 1]tri_selection(t5)assert t5 == [1, 2, 3, 4, 5], "Trié à l'envers"
t6 = [7, 7, 7, 7]tri_selection(t6)assert t6 == [7, 7, 7, 7], "Éléments identiques"
print("Tous les tests passent !")3.5 Le module doctest
Section intitulée « 3.5 Le module doctest »Le module doctest permet d'inclure les tests directement dans la docstring de la fonction, sous forme d'exemples d'utilisation :
def factorielle(n): """Calcule n! (factorielle de n).
Preconditions: n est un entier >= 0.
>>> factorielle(0) 1 >>> factorielle(1) 1 >>> factorielle(5) 120 >>> factorielle(10) 3628800 """ if n == 0: return 1 resultat = 1 for i in range(1, n + 1): resultat = resultat * i return resultat
if __name__ == "__main__": import doctest doctest.testmod()Si tous les tests passent, rien ne s'affiche. Avec l'option -v (verbose), on obtient le détail :
Trying: factorielle(0)Expecting: 1ok...4 tests in 1 items.4 passed and 0 failed.Test passed.4. Développement piloté par les tests (TDD)
Section intitulée « 4. Développement piloté par les tests (TDD) »Le TDD (Test Driven Development) est une méthode de développement qui consiste à :
- Écrire les tests d'abord (ils échouent car la fonction n'existe pas encore)
- Écrire le code minimal pour que les tests passent
- Améliorer le code si nécessaire (les tests garantissent qu'il reste correct)
# Étape 1 : écrire les testsassert est_palindrome("kayak") == Trueassert est_palindrome("python") == Falseassert est_palindrome("") == Trueassert est_palindrome("a") == Trueassert est_palindrome("aba") == Trueassert est_palindrome("ab") == False
# Étape 2 : écrire la fonctiondef est_palindrome(mot): """Vérifie si mot est un palindrome.""" return mot == mot[::-1]
# Étape 3 : tous les tests passentÀ retenir
Section intitulée « À retenir »- Spécifier une fonction, c'est décrire ce qu'elle fait, ses préconditions (contraintes sur les entrées) et ses postconditions (garanties sur le résultat).
- Le prototype donne le nom, les paramètres, leurs types et le type de retour.
- La docstring décrit le comportement de la fonction sous la ligne
def. - Les assertions (
assert condition) vérifient les préconditions et les postconditions. En cas d'échec, le programme s'arrête. - Un jeu de tests vérifie le bon fonctionnement de la fonction avec des cas normaux, limites, extrêmes et d'erreur.
- Le module
doctestpermet d'intégrer les tests dans la docstring. - Le TDD consiste à écrire les tests avant le code.
© 2026 Corentin Dupont - LyCode
Créé avec Astro Starlight