Chat
Chat

Par Diego - Le 6 avril 2024

Temps de lecture estimé: 6 minutes

Typescript

test

documentation

L'importance des doctests en Typescript

La documentation est un fléau que tous les développeurs se sont déjà infligés. Bien que nécessaire à la maintenance d’un projet, elle pose certains problèmes très courants, tels que la répétition avec le code, du temps supplémentaire à la rédaction, mais surtout, un risque de décallage avec le code. Il est très fréquent qu’un développeur modifie du code mais oublie de modifier les commentaires et la documentation.
Et comme le dit le jargon:

Mieux vaut aucune documentation qu’une fausse documentation

Il s’agit là en fait de la problématique de non régression. Une des pratiques les plus courantes pour y pallier est l’utilisation de tests. Les adeptes du TDD (Test Driven Development) diront que les tests permettent de tester, mais également de concevoir et de documenter le code. Mais soyons honette; à l’arrivée sur un nouveau projet, quel developpeur va lire les tests?

C’est pour cela que beaucoup de languages ont introduit les doctests, nottament Python ou Rust. Ceux-ci permettent d’écrire des exemples lisibles dans la documentation, et que ceux-ci soient executés et testés!
Non seulement cela évite le décallage entre le code et les commentaires, mais en plus, il s’agit d’un moyen très efficace pour faire comprendre l’utilité d’un bout de code.

L’exemple n’est pas le meilleur moyen pour communiquer, c’est le seul

—Gandhi

Ce qui existe déjà

La norme TSDoc contient déjà un tag @example, permettant de spécifier des examples. Le problème, c’est que personne n’a implementé d’outil pour les exécuter... Enfin, il y a quelques librairies, mais toutes posent des problèmes conséquents à nos yeux.

  • doctest et doctest-js ne supportent pas TypeScript.
  • typescript-expect-plugin, la syntaxe se rapproche plus de tests unitaires que d’exemples dédiés à la compréhension. Surtout, les imports de modules ne sont pas supportés dans le fichier testé, ce qui rend l’outil inutilisable en pratique.
  • doctest-ts et tsdoc-testify nécessitent la création de fichiers annexes qui contiennent le code des tests. Cela nous ramène au problème de régression, puis-ce qu’il faut s’assurer que ces fichiers sont toujours à jour avec les doctests en commentaire.

Le problème avec les doctests en Typescript

Il y a de nombreux problèmes liés à la création d’une telle librairie, d’autant plus dans l’écosystème de typescript. Notamment:

  • Les exemples étant dans des commentaires, ils ne font pas partie de l’AST. Ils ne sont donc pas parsés ni type-checké comme du code. Il va donc falloir compiler et exécuter ces bouts de codes nous-même.
    De plus, on devra nécessairement lire les fichiers source comme du texte afin de récupérer les commentaires.
  • On peut executer les tests de l’intérieur ou de l’extérieur du module. C’est à dire, soit on transforme le module pour qu’il exécute lui même ses propres tests, soit on importe le module et on appelle ce qu’il y a à tester. Nous adopterons l’approche par l’intérieur car cela est plus simple, et permet d’accéder à des symboles non exportés.
  • Un fichier source peut prendre plein de formes différentes: TypeScript, JSX, CommonJS, module...
  • Les imports sont configurés par le projet courant. Ils peuvent:
    • Être relatifs :
      import { f } from '../../utility.ts'
    • Être aliasés par le fichier tsconfig :
      import user from '@services/user.ts'
    • Provenir de librairies:
      import { filter } from 'ramda'
    Afin que les imports relatifs fonctionnent, il faut donc que les doctests s’exécutent comme si on exécutait le module original, à la même position dans l’arborescence de fichiers.
  • Il peut y avoir des différences en fonction de l’environnement d’éxécution. Par exemple, le compilateur Next (qui utilise swc), applique des transformations aux imports d’images et de css, et il ne semble pas possible d’éxecuter un module isolé avec l’environnement Next. Il est donc important que l’utilisateur final puisse configurer l’environnement d’éxecution des tests.
    Ceci dit, on aimerait que la librairie soit le plus plug-and-play possible, et fonctionne par défaut dans le plus grand nombre d’environnements.

L’éxecution des tests

Au départ, l’approche lors du lancement d’un test, était de transformer le code source, l’écrire dans un fichier temporaire, éxecuter ce fichier puis le supprimer. Cela permettait de résoudre toutes les contraintes évoquées ci-dessus. Je voulais initialement utiliser ts-node en mode interactif pour éviter d’écrire un fichier sur disque, mais cela ne permettait pas de se placer à un endroit dans l’arborescence du projet, et donc les imports relatifs ne fonctionnaient pas.

Ensuite, j’ai découvert les loaders dans les nouvelles versions de node. Cependant, ceux-ci ne permettent pas de transformer un programme entier, ils sont plus faits pour gérer quelques cas particuliers d’imports. Je voulais m’en servir tout de même pour me débarasser des imports non-voulus (images, css...), mais cela ne fonctionnait pas pour tous les environnements (par exemple en CommonJS).
De plus, au jour ou j’ecris cet article, les loaders sont encore très expérimentaux et il y a très peu de documentation.

Enfin, j’ai découvert ts-patch. Il s’agit d’un outil pour exécuter du typescript en appliquant certaines transformations d’AST. C’est exactement ce qu’il nous faut! J’ai donc écris plusieurs transformateurs pour ts-patch:

  • Un pour parser et exécuter les doctests en commentaires
  • Un pour éliminer les imports non-voulus (images et autres)

Le résultat final

Tout ce travail nous amène à notre librairie NPM, The Real Doctest, que l’on peut facilement utiliser pour lancer des doctests.

Cela est déjà très pratique, mais nous irons encore un cran plus loin en développant une extension VSCode pour lancer les tests directement depuis l’IDE.

extension-vscode

Des idées ? un projet ?

N'hésitez pas à nous en faire part

Nous vous répondrons dans l'heure