XSLT et Test-Driven-Development : Le bonheur !

Je travaille en ce moment sur un convertisseur MarkDown vers LibreOffice. Pas le sujet du siècle, mais un vrai besoin chez OXiane, pour produire les cahiers de TP de nos cours.

Première version, faite à l’arrache, utilisation d’une librairie Java qui convertit le MarkDown en HTML, puis utilisation d’un traitement Xslt 3.0 pour convertir le Html dans le Xml qui va bien pour un document LibreOffice.

Cette première version fonctionne, mais on a quelques soucis, liés aux puces de niveau 2. Autant, dans un document texte, dans n’importe quel Word Processor, vous mettez vos puces de n’importe quel niveau où vous voulez, mais en Html (je parle de Html, pas de Css, hein !), une puce de niveau 2 ne peut être que dans une puce de niveau 1, et il faut du texte dans la puce de niveau 1. Bref, ça fait pas ce qu’on veut.

Comme en même temps, je suis en train de rédiger le cours Xslt 3.0, je me dis que ça doit être simple de convertir directement le MarkDown en Odt entièrement en Xslt, sans utiliser de code Java. Et effectivement, c’est simple.

Par ailleurs, mon livre de chevet actuel est Clean Coder, de Bob C. Martin, dans lequel l’importance du Test Driven Development est présentée.

3 règles simples :

  1. tu ne peux pas écrire de code de production, sans avoir auparavant écrit un test unitaire qui échoue
  2. tu ne peux pas écrire plus d’un test unitaire qui soit suffisant pour échouer, et une erreur de compilation est un échec
  3. tu ne peux pas écrire plus de code de production que nécessaire pour faire passer le test unitaire qui échouait.

Des puristes me diront que c’est mal traduit, je l’accepte.

Bref, tout ça mis ensemble, je tente d’écrire ma nouvelle Xsl en pur TDD, histoire d’évaluer les intérêts de la méthode. Outillage : Maven, parce que je trouve ça bien, et qu’au final, je suis en train de développer un plugin Maven ; XSpec qui est un framework de test unitaire pour Xslt, XQuery et Schematron, et xspec-maven-plugin, parce que c’est moi qui le maintient, donc…

Démarrage

  • comment vérifier que ma Xsl se lance et traite bien le fichier md passé en paramètre ? Application de la première loi, écriture d’un test unitaire qui lance la Xsl avec une Url en paramètre, et qui attend une sortie. Exécution du test, ça plante, la Xsl n’existe pas !
  • Création de la Xsl, définition du paramètre, création du template xsl:initial-template, écriture de l’URL passée en paramètre. Exécution du test, qui passe, Super !
  • Travail sur les titres : création d’un test unitaire qui vérifie que la fonction prv:isTitle1 renvoie true quand on lui passe # Title. Exécution du test, qui plante, la fonction n’existe pas !
  • Ecriture de la function :
    <xsl:function name="prv:isTitle1" as="xs:boolean">
    <xsl:param name="s" as="xs:string"/>
    <xsl:sequence select="$s eq '# Title'"/>
    </xsl:function>

    Ok, c’est pas d’une grande intelligence, mais selon la troisième loi, je n’ai pas le droit d’écrire plus de code que nécessaire pour faire passer le test unitaire !

  • Titres toujours, écriture d’un TU qui vérifie que prv:isTitle1('# Ceci est un titre') renvoie true. Exécution, échec, je commence à comprendre…
  • Modification de la fonction :
    <xsl:function name="prv:isTitle1" as="xs:boolean">
    <xsl:param name="s" as="xs:string"/>
    <xsl:sequence select="$s=>starts-with('# ')"/>
    </xsl:function>

    Exécution, succès !

  • Vérification que les titres de niveau 2 ne renvoient pas true. Ecriture du TU avec appel de prv:isTitle1('## Title'). Exécution, succès, interdiction de modifier le code de production.

A un moment, je décide que cette Xsl ne produira pas directement le Xml attendu par LibreOffice, mais un format intermédiaire, qui me permettra de faire le groupement des lignes de texte en paragraphes ou en blocs de code. Modification d’un test unitaire, exécution, échec, correction de la Xsl. Modification d’un second TU, exécution, échec, modification de la Xsl, exécution, succès. Et là, un grand plaisir : comme tous les tests unitaires passent avec succès, j’ai la garantie que mon code répond au besoin exprimé ! Et c’est très rassurant !

Au final, une XSL de 128 lignes, un XSpec de 393 lignes. Et un bout de code que je n’ai jamais lancé en vrai ! Va-t-il fonctionner avec un vrai fichier en entrée ?

Ecriture d’un dernier TU avec un vrai fichier en entrée, écriture du résultat attendu dans le <x:expect>, et le test échoue, je reçois l’URL de mon fichier ! Ah, ben oui, c’était mon tout premier TU… et donc mon tout premier bloc de code. Retravail sur le xsl:initial-template pour appliquer les règles de transformation sur toutes les lignes du fichier, exécution, et : SUCCÈS !

En conclusion :

  • je n’ai pas écrit une ligne de code inutile, puisque que chaque ligne de code est justifiée par un test unitaire, soit une expression de besoin
  • j’ai la capacité de refactoriser mon code en toute sérénité, j’ai des tests unitaires pour vérifier que je casse rien
  • exécution des TU avec un code-coverage : 100% du code est testé. De ma vie, je n’ai jamais atteint ça !
  • mon code fonctionne parfaitement, alors que je n’ai même pas écrit le code qui permet de le lancer ! Cela facilitera grandement l’intégration avec le code Java, ou à terme, avec n’importe quel autre code !
  • J’y ai passé plus de temps que si je n’avais pas procédé en TDD. Certes. Mais je n’ai jamais non plus eu autant confiance dans le code que j’ai écrit, et ça, ça n’a pas de prix !

Test-Development-Driven adopté !

 

[1] The Clean Coder: A Code of Conduct for Professional Programmers, Robert C. Martin, Addison-Wesley 2011
[2] XSpec : A unit test and behaviour-driven development (BDD) framework for XSLT, XQuery, and Schematron. Jeni Tennison, Florent George, AirQuick, 2008
[3] xspec-maven-plugin : A maven plugin to run XSpec unit tests. Adam Retter, Christophe Marchand, 2017