Le doctest, une technique anti-chat

Le doctest, une technique anti-chat

Un petit article pour comprendre les doctests en Python.

Pourquoi ?

Peu nombreux sont les langages de programmation qui proposent le principe du doctest. Un doctest est une documentation testable, ou un test documenté. Si vous faites du Python, il faut en profiter ! Un projet logiciel ne commence JAMAIS par du code. Il commence par une idée. Cette idée doit d'abord être jetée sur le papier en anglais, en islandais, en turc, en japonais ou ce que vous voulez. Ouvrez un éditeur de texte puis commencez par écrire ce qui vous passe par la tête à propos de votre projet.

Voici un exemple:

Le but de ce logiciel est de contrôler le niveau de croquettes dans
l'assiette de mon chat et de compter le nombre de fois par jour où il mange.
Le but final non avouable est de ne plus avoir à lui filer à manger
en concevant un système automatique à base d'Arduino. Je dois donc
avoir un capteur pour le poids de l'assiette et un détecteur de présence. Il y aura donc deux composants : un pour chaque type de capteur.

Une fois que votre idée commence à se préciser, des bouts de code vont germer dans votre esprit. C'est le moment de les écrire ! À la suite de votre introduction, vous pouvez alterner des paragraphes en français et des paragraphes en Python, en reproduisant une session Python interactive :

Pour pouvoir accéder à chaque capteur, on peut donc imaginer avoir une classe
pour chaque type de capteur::

  >>> from marducha.capteur import Presence
  >>> capteur1 = Presence()
  >>> capteur1.get_presence()
  False

Et pour l'autre capteur :

  >>> from marducha.capteur import Poids
  >>> capteur2 = Poids()
  >>> capteur2.get_poids()
  0.35
💡
Update 2023 : ce genre de doctest est fondamentalement ce qui est utilisé de nos jours dans les Jupyter Notebooks, qui ne sont pas des tests, mais des sortes de documentations ou expérimentations Python éditables en ligne, idéale pour les data scientists.

Presence et Poids n'existent pas encore, mais vous êtes déjà en train de réfléchir à la façon de les utiliser. Le simple fait d'écrire des exemples d'utilisation de votre futur code vous aide à repérer prématurément les erreurs ou les difficultés de conception : vous avez déjà économisé un premier tour de refactoring ! Par exemple vous pouvez immédiatement réaliser que vous aurez besoin de passer des paramètres pour configurer l'accès au capteur physique ou de configurer une échelle d'unité pour le poids.

Et à l'instant même où vous comprenez que vous allez devoir aussi gérer une boucle d'événements pour réagir au capteur de présence, votre chat vous saute subitement sur les genoux et vous rappelle qu'il est prêt à vous ouvrir les yeux, au sens propre, si vous ne remplissez pas son assiette au plus vite. Après un aller-retour à la cuisine, vous avez tellement pesté contre votre animal bien-aimé, que vous avez complètement perdu le fil de votre pensée. Le doctest est une technique anti-chat : relisez-le rapidement depuis le début, vous allez revenir subitement dans le même état d'esprit qu'avant la perturbation. Vous allez retrouver vos idées et pouvoir reprendre votre réflexion sans perte de temps. Les niveaux d'abstraction nécessaires pour mener à bien une conception logicielle s'accommodent très mal des distractions et des changements de contexte fréquents. Utiliser des doctests dès la phase de conception vous offre déjà deux avantages non négligeables :

  1. détection précoce des erreurs de conception
  2. rapidité de reprise du travail

Comment ?

La prise en charge des doctests est offerte dans la bibliothèque standard. Enregistrez le texte que vous avez écrit dans un fichier README.txt, puis créez un fichier test.py avec le contenu suivant :

import doctest
doctest.testfile('README.txt')

Si vous l'exécutez, avec python test.py, vous constaterez que le doctest est exécuté et génère des erreurs. Pour corriger ces erreurs, il suffit d'écrire l'implémentation de votre module capteur ! Créez un package marducha contenant un module capteur.py avec les définitions suivantes (pour rappel, un package est un simple répertoire contenant un fichier vide __init__.py , et un module est un simple fichier Python) :

"""
Module contenant les capteurs.
"""

class Presence():
    """Classe permettant d'interroger le capteur de presence

    >>> p = Presence()
    >>> p.get_presence()
    False
    """

    def get_presence(self):
        """le chat est-il present ?
        """
        return False


class Poids():
    """Classe pour mesurer le poids
    """
    def get_poids(self):
        """Combien pese l'assiette ?
        """
        return 0.35

Maintenant votre implémentation est correcte vis-à-vis du test : vérifiez-le en lançant python test.py. Notez qu'il est possible de mettre des doctests à l'intérieur du code python, dans les docstrings, c'est à dire les chaînes de documentation situées au début des modules, des classes ou des fonctions. Pour tester les docstrings d'un module, vous pouvez simplement ajouter dans votre fichier test.py :

from marducha import capteur
doctest.testmod(capteur)

Les fonctions testmod et testfile constituent l'API simplifiée du module doctest. Consultez la documentation pour connaître l'API avancée et l'API unittest du module doctest.

Conclusion

Au fil du temps, vous constaterez que les doctests vous offrent des avantages supplémentaires : vous pouvez plus facilement concevoir à plusieurs, échanger et fixer des idées. Vous réaliserez aussi que vous aurez écrit de la documentation presque sans vous en rendre compte, et que cette documentation est à jour. Il suffira ensuite de la publier grâce à un outil comme Sphinx.

Le doctest cumule ainsi les avantages du développement dirigé par les tests (TDD) et de ce qu'on pourrait appeler du développement dirigé par la documentation (DDD). Mais gardez à l'esprit qu'un doctest est avant tout une documentation testée, plus qu'un test documenté. Il doit se contenter de donner des exemples de code significatifs et d'assez haut niveau et doit aider le lecteur à comprendre l'architecture du projet. Il ne doit en aucun cas remplacer de vrais tests unitaires exhaustifs !

💡
Si vous écrivez des tests, vous avez intérêt à ce qu'ils soient lancés automatiquement par une infrastructure d'intégration continue (CI). Vous aurez ainsi moins de régression et moins de chance que les autres cassent votre code. Donc une meilleure qualité et satisfaction à tous les étages.

Si le sujet vous intéresse,