Doctest, la documentation qui test le code Python
Il y a deux composants indispensables pour une bonne qualité du code, mais qui sont souvent négligés : la documentation et les tests. Ils sont difficiles à imposer et surtout à maintenir. Ceci fait que les approches dites Agile tendent à mettre en retrait la documentation par rapport à la production logicielle.
Il faut admettre que même lorsqu’on trouve une documentation ou des exemples sur du code, on est méfiant car on ignore si ils sont à jour.
Python propose une approche intéressante qui permet de documenter le code tout en s’assurant que cette documentation est à jour : doctest.
Le point d’entrée : les Docstring.
Un code source Python accepte deux types de commentaires : d’une part les commentaires de fin de ligne commençant par le symbole #, d’autre part les documentations de modules, classes et méthodes : les docstring.
Le docstring est placé en début de fichier pour documenter le module et juste après la déclaration pour documenter classe, méthode ou fonction (et est donc indenté par rapport à la déclaration). Le contenu de la docstring est encadré de triples guillemets. Son contenu est régit par des conventions documentées par la PEP-257. Ainsi, si le doctring ne contient qu’une seule ligne, les deux triples guillemets doivent également être sur la même ligne. Si elle nécessite plusieurs lignes, alors la première contient un résumé de la documentation. Elle est suivi par une ligne vide puis le reste de la documentation avant d’ajouter une ligne vide avant les triples guillemets fermant. Voici un exemple en utilisant les deux implémentations de la suite de fibonacci de la documentation de Python :
def fib(n): # write Fibonacci series up to n """Print a Fibonacci series up to n.""" a, b = 0, 1 while a < n: print(a), a, b = b, a + b def fib2(n): # return Fibonacci series up to n """ Return a list containing the Fibonacci series up to n :param n: Limit value for the Fibonacci serie. :return: A tuple with the calculated values. """ result = [] a, b = 0, 1 while a < n: result.append(a) # see below a, b = b, a+b return result
Documentation et commentaires.
Avant d’aborder les questions d’utilité, une considération sur les utilisations. Les docstring servent à documenter un module, une classe, une méthode ou une fonction. Leur place est imposée. Les commentaires peuvent être insérés n’importe où dans le code. Ceci, associé à certains outils qui seront décrits ci-après, va décrire les règles d’usage :
- Les docstrig étant exposés aux utilisateurs, elles doivent expliquer comment utiliser le module, la classe ou la méthode. Pour cela, il elles doivent résumer ce que réalise la fonction, détailler les variables et préciser ce qui est attendu et ce qui sera retourné. Elles permettent d’exploiter le composant sans avoir à analyser son implémentation.
- Les commentaires ne sont accessibles que dans le code et sont destinés aux développeurs. Ils doivent expliquer pourquoi le code est présent. Ces commentaires concernent donc en général quelques lignes dont la présence ou le sens pourrait être sujet à question.
Exploitation des docstrings.
Il existe pour le développeur deux moyens pour accéder aux docstring : l’attribut __doc__, ou la fonction native help().
>>> import fiboModule >>> fiboModule.fib.__doc__ 'Print a Fibonacci series up to n.'
En dehors de cette exploitation immédiate, de nombreux outils tirent profit des docstrings. Exécutez donc la commande suivante dans un shell :
pydoc fiboModule
Pydoc est un petit utilitaire qui permet beaucoup de choses autour de la documentation, je vous invite à consulter sa page de documentation. Pydonc n’est qu’un outil parmi d’autres, Sphinx est un autre exemple d’outil exploitant les docstrings.
Doctest, l’assurance qualité de la documentation.
Malgré la présence des outils présentés précédemment, rien ne garanti l’adéquation entre la documentation et l’implémentation. C’est là qu’entrent en jeu le module doctest. Comme je l’ai écris précédemment, ce que l’on attend surtout d’une documentation, c’est de savoir comment utiliser le composant. Quoi de mieux que de présenter des cas d’utilisation, et de préférence des cas limite. Modifions donc le module avec le contenu suivant (seul la première fonction est présentée) :
def fib(n): # write Fibonacci series up to n """ Print a Fibonacci series up to n. >>> fib(0) >>> fib(1) 0 >>> fib(2) 0 1 1 >>> fib(3) 0 1 1 2 >>> val = 4 >>> fib(val) 0 1 1 2 3 """ a, b = 0, 1 while a < n: print(a), a, b = b, a + b if __name__ == "__main__": import doctest doctest.testmod()
D’un point de vue documentation, nous avons complété la docstring en incluant des cas d’utilisation. Ces cas sont présentés exactement comme si nous avions saisi une série d’instructions sur le shell Python et obtenu un affichage de résultat. Notez qu’il peut y avoir un enchainement de plusieurs commandes, comme l’affectation de variables (ligne 11).
Nous avons également ajouté 3 lignes importantes, les trois dernières (21 à 23). La condition exécute le corps si le module a été effectivement exécuté, en opposition à l’exploitation standard où le module sera simplement chargé. C’est alors qu’intervient doctest. Doctest va parcourir les docstrings à la recherche de tout ce qui ressemble à une session interactive, exécuter ce qui ressemble à une commande et vérifier que la sortie attendue correspond à la sortie effective. Si ce retour ne correspond pas, une erreur est affichée. Ainsi, si l’implémentation a évolué et que la documentation n’a pas été maintenu à jour, doctest le révèlera. Doctest peut aussi être utilisé pour vérifier que l’évolution n’a pas introduit de régressions. On peut donc dire que doctest permet de réaliser une série de tests unitaires sur la fonction documenté.
Bien entendu, doctest n’est pas un outil ultime, et il faut bien avoir conscience des limites. Doctest n’est pas réellement un outil de test unitaires, pas aussi complet que unittest, il ne permet que de s’assurer des cas « simples ». En effet, construire des mocks finirait par nuire à la documentation. Cependant, doctest souffre de la même limite que les tests unitaires : il ne garantissent le bon comportement que dans la limite de ce à quoi ils sont prévus. Ainsi, la qualité et la pertinence de la documentation dépend de celui qui l’aura écrite.
En conclusion
Avec les docstring et doctest, il est possible d’écrire en Python une documentation qui permet d’expliquer le fonctionnement d’une fonction, et de s’assurer de la validité de celle-ci. L’orientation « cas d’utilisation » permet bien d’orienter cette documentation vers ce qu’elle doit être : une explication de comment utiliser la fonction.
À propos de... Darko Stankovski
iT guy, photographe et papa 3.0, je vous fais partager mon expérience et découvertes dans ces domaines. Vous pouvez me suivre sur les liens ci-dessous.