Une page de Le Lab de la taverne

Hero image les tests à la .Taverne de l'Invisible

La documentation Python fiable avec doctest

Par Darko Stankovski, 19/12/2011 (Mise à jour 17/07/2026) — Débutant

Il y a deux composants indispensables pour garantir la qualité d'un code, mais qui sont pourtant trop souvent négligés : la documentation et les tests. Difficiles à imposer et encore plus complexes à maintenir dans le temps, ils passent fréquemment au second plan. C'est d'ailleurs pour cela que certaines approches Agiles tendent à mettre en retrait la documentation au profit de la production logicielle pure.

Même face à un code documenté ou illustré par des exemples, on reste souvent méfiant. Cette documentation est-elle encore à jour ? Le code n'a-t-il pas changé depuis sa rédaction ?

Pour résoudre ce problème, Python propose une approche élégante qui permet de documenter le code tout en s'assurant que cette documentation reste vivante et exacte : le module doctest.

Le point d’entrée : Les Docstrings

Un code source Python accepte deux types de textes explicatifs :

  • Les commentaires du code : Commençant par un croisillon #, ils sont destinés aux développeurs pour expliquer le pourquoi d'une ligne de code complexe.
  • Les docstrings : Destinées à documenter les modules, classes et fonctions. Elles décrivent comment utiliser le composant sans avoir à lire son implémentation.

Placée juste après la déclaration d'une fonction ou d'une classe (et donc indentée), la docstring est encadrée par des triples guillemets """. Son contenu est régi par les conventions de la PEP 257.

Voici un exemple issu de la documentation de Python de structure correcte de fonctions et leurs documentations :

def fib(n):    # write Fibonacci series less than n
    """Print a Fibonacci series less than n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b
    print()

def fib2(n):  # return Fibonacci series up to n
    """Return a list containing the Fibonacci series up to n."""
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)    # see below
        a, b = b, a+b
    return result

Comment exploiter ces docstrings ?

Le développeur peut accéder à cette documentation depuis le terminal Python de deux manières : via l’attribut spécial (dunder) __doc__ ou via la fonction native help().

>>> import fiboModule
>>> print(fiboModule.fib.__doc__)
Print a Fibonacci series less than n.

Avec des outils modernes comme PyCharm ou VisualStudio Code, cette documentation est affichée lorsqu'on survole le nom de la fonction.

Enfin, de nombreux outils de génération automatique de documentation (comme pydoc ou Sphinx) extraient ces docstrings pour créer des pages web ou des fichiers PDF complets.

Doctest : L'assurance qualité de votre documentation

Disposer d'outils de génération de la documentation ne garantit pas que cette dernière est fidèle à la réalité du code. C’est ici qu’intervient le module doctest.

Une bonne documentation devrait avoir des exemples concrets d'utilisation. On peut inclure un exemple copié/collé du shell intéractif et les outils de génération de docs ont même des directives pour ça (je ne les indiquerai pas ici). doctest va simplement exploiter ce contenu.

Modifions notre fonction fib2 pour y intégrer ces exemples :

def fib2(n):
    """
    Return a list containing the Fibonacci series up to n.

    >>> fib2(0)
    []
    >>> fib2(3)
    [0, 1, 1, 2]
    >>> limite = 5
    >>> fib2(limite)
    [0, 1, 1, 2, 3]
    """
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a + b
    return result

if __name__ == "__main__":
    import doctest
    doctest.testmod()

En supposant que ce fichier s'appelle demo_fibo.py, dans un terminal, il suffit de lancer :

python -m doctest demo_fibo.py

Commande qui ne renverra rien car tout est ok… Mais qui accepte le flag -v si besoin.

Comment ça marche ?

Les lignes précédées de >>> simulent des commandes saisies dans un interpréteur Python. La ligne immédiatement suivante représente le résultat attendu.

Le module doctest analyse les docstrings, exécute les commandes et vérifie que la sortie correspond au caractère près à ce qui est écrit.

Si le code évolue et casse la logique, doctest lèvera une erreur détaillée lors de l'exécution. Si nous remplaçons while a < n par while a <= n ligne 15 sans modifier l'exemple, nous aurons :

**********************************************************************
File "demo_fibo.py", line 6, in __main__.fib2
Failed example:
    fib2(3)
Expected:
    [0, 1, 1, 2, 3]
Got:
    [0, 1, 1, 2, 3, 5]
**********************************************************************

En soi, c'est le minimum que vous avez besoin de connaitre pour exploiter doctest et redonner confiance dans la documentation que vous avez écrit. Mais il y a quelques astuces à ajouter pour tirer pleinement parti de ce module.

Quelques fonctionnalités méconnues

Gérer le texte dynamique et les espaces (Les directives)

Vous rencontrerez vite des cas où une comparaison stricte "au caractère près" devient bloquante. Par défaut, le moindre espace ou retour à la ligne inattendu fait échouer doctest. De même, si votre fonction renvoie une valeur dynamique (comme un identifiant unique ou une adresse mémoire), le test échouera à chaque exécution.

Pour assouplir cela, Python propose des directives à placer en fin de ligne :

  • # doctest: +NORMALIZE_WHITESPACE : Ignore les différences d'espaces consécutifs et les retours à la ligne.
  • # doctest: +ELLIPSIS : Permet d'utiliser ... (Ellipsis dont je vous ai déjà parlé) pour remplacer une partie dynamique du texte.
def generer_rapport(nom):
    """
    Génère un court rapport avec un identifiant dynamique.

    >>> generer_rapport("Alice") # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS
    Rapport pour : Alice
    Généré à : ...
    Statut   : OK
    """
    from datetime import datetime
    heure = datetime.now().strftime("%H:%M:%S")
    return f"Rapport pour : Alice\nGénéré à : {heure}\nStatut   : OK"

Tester les cas d'erreurs (Les Exceptions)

Une bonne documentation peut aussi expliquer le comportement qui conduit à la levée d'une exception. Il suffit alors d'ajouter la trace de l'exemple qui suffit à doctest.

En réalité, doctest ne comparera que la première ligne (l'en-tête) et la ligne de détail de l'exception qui est généralement la dernière (mais peut s'étendre sur plusieurs). Tout ce qui est entre les deux est ignoré et à juste titre puisqu'il y a des parties qui peuvent varier comme les numéros de ligne d'appel.

Pour simplifier la documentation, la pratique est d'utiliser l'ellipse ... entre ces deux lignes. Il n'est pas nécessaire de faire appel à la directive, elle est activée pour les exceptions :

def diviser(a, b):
    """
    Divise deux nombres.

    >>> diviser(10, 0)
    Traceback (most recent call last):
    ...
    ZeroDivisionError: division by zero
    """
    return a / b

Utiliser Pytest pour tester la documentation

Les tests de la documentation sont des tests comme les autres. Historiquement, on les exécutait par module et vous trouverez encore souvent ce type de code :

if __name__ == "__main__":
    import doctest
    doctest.testmod()

Mais lancer manuellement chaque script, directement ou avec python -m doctest, devient vite fastidieux sur un gros projet. Heureusement, le framework de test de référence pytest intègre nativement le support de doctest.

Il vous suffit de lancer pytest avec l'option suivante pour qu'il analyse, découvre et exécute à la fois vos tests classiques et vos tests de documentation :

pytest --doctest-modules

Il est évidemment indispensable que cette option soit active dans les usine de développement (CI/CD). L'idéal serait donc de paramétrer Pytest dans le pyproject.toml :

[tool.pytest.ini_options]
addopts = "--doctest-modules"

testpaths = [
    "src",
    "tests"
]

Une subtilité : nous allons devoir ajouter les sources des projets à testpaths car nous avons maintenant des tests dans les sources.

À noter qu'il est même possible de configurer via la configuration Pytest d'utiliser automatiquement les directives sur les espaces et les ellipses :

[tool.pytest.ini_options]
doctest_optionflags = [
    "NORMALIZE_WHITESPACE",
    "ELLIPSIS",
    "IGNORE_EXCEPTION_DETAIL"
]

Les limites de l'outil

Il est indispensable de garder à l'esprit que doctest sert avant tout à valider les exemples de la documentation et n'a pas pour vocation de remplacer un framework de tests unitaires complet comme pytest ou unittest. Il est naturellement limité à des cas simples d'illustration. De vrai cas de test complets conduiraient à rendre la documentation illisible.

En résumé

Avec le module doctest, Python offre une solution pragmatique au problème de la documentation obsolète. En illustrant la documentation de cas d'utilisation, non seulement nous avons une documentation plus complète mais nous pouvons également vérifier qu'elle correspond au code.

Les doctest ne remplacent pas les tests unitaires (et autres), ce n'est pas un outils destiné au suivi de la qualité de code pour le développeur qui maintient ce code, c'est un outil de confiance pour le développeur qui utilise ce code.

Licence Creative Commons

Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Pas de Modification 4.0 International