XSpec en pratique : tester une feuille XSLT
XSpec décrit ce qu'une feuille de style doit produire, dans un fichier XML séparé. Le premier test utile tient en quinze lignes.
XSpec décrit ce qu'une feuille de style doit produire, dans un fichier XML séparé qui ne touche pas au code testé. C'est ce qui permet de le tenir à jour : un test écrit dans la feuille disparaît à la première refonte, et personne ne s'en aperçoit.
Le premier test utile tient en quinze lignes. La moitié du travail consiste ensuite à savoir ce que « il passe » veut dire exactement.
Le premier test
<x:description stylesheet="facture.xsl"
xmlns:x="http://www.jenitennison.com/xslt/xspec">
<x:scenario label="une facture sans ligne">
<x:context>
<facture/>
</x:context>
<x:expect label="produit un total à zéro">
<total>0</total>
</x:expect>
</x:scenario>
</x:description>
x:context fournit le document d'entrée, x:expect décrit le résultat attendu. Le scénario
échoue si les deux arbres diffèrent.
« Les deux arbres », et pas « les deux fichiers »
🔴 La comparaison porte sur l'arbre du résultat, avant sérialisation. Rien de ce qui se décide au moment d'écrire les octets n'est vu : ni l'encodage, ni la déclaration XML, ni l'indentation. Une feuille dont la sortie s'ouvre en caractères illisibles passe la suite XSpec au vert — ces causes-là sont du côté de la sérialisation, et le test ne traverse pas cette étape.
⚠️ Les nœuds de texte, en revanche, sont dans l'arbre. Un x:expect réindenté pour rester
lisible introduit des blancs que le résultat n'a pas, et le scénario échoue sur une différence
qui ne se voit pas dans le rapport. C'est le mécanisme décrit dans
les espaces blancs en XSLT, appliqué cette fois au fichier de
test lui-même.
Pour ne comparer qu'une partie du résultat, select évalue une expression sur ce qui a été
produit :
<x:expect label="le total seul" select="/facture/total" test="xs:integer(.) eq 0"/>
⚠️ L'inversion classique est de croire que ce select porte sur le contexte d'entrée. Il porte
sur la sortie.
Les scénarios s'emboîtent
Un scénario imbriqué hérite du x:context de son parent, ce qui évite de recopier l'entrée pour
chaque assertion :
<x:scenario label="une facture à deux lignes">
<x:context href="facture.xml"/>
<x:scenario label="le total"><x:expect select="/facture/total"/></x:scenario>
<x:scenario label="le nombre de lignes"><x:expect select="count(//ligne)"/></x:scenario>
</x:scenario>
⚠️ Un enfant qui redéclare x:context remplace celui du parent, il ne le complète pas. Et un
href relatif se résout par rapport au fichier .xspec, jamais par rapport au répertoire d'où
la commande est lancée.
Trois façons de passer sans rien vérifier
🔴 Un test vert n'est une information que si le rouge était possible. Trois cas rendent exactement le signal attendu sans avoir rien mesuré :
| Le cas | Ce que le rapport affiche |
|---|---|
scénario sans x:expect | succès, zéro assertion |
scénario marqué pending | ignoré, compté à part |
focus posé sur un autre scénario | les autres ne sont pas joués |
Les deux derniers sont signalés dans le rapport HTML — encore faut-il l'ouvrir. ⚠️ Un pending
posé pour une après-midi et oublié se comporte comme un test réussi pour qui ne lit que la
couleur d'ensemble.
Ce qu'un test vert ne dit pas
- il ne dit rien des performances : la même sortie en dix secondes ou en dix minutes passe pareil ;
- il ne dit rien du fichier produit, seulement de l'arbre ;
- il ne dit rien des entrées que vous n'avez pas écrites, et c'est la limite de tout test par l'exemple.
Pour un premier essai, il est plus rapide d'isoler une règle que de jouer la feuille entière : voir tester une règle XSLT isolée.
Ce que cet article ne couvre pas
- les tests de fonctions et de paquets XSLT 3.0, qui ont leur propre syntaxe d'appel ;
- l'exécution en intégration continue, où le code de sortie compte plus que le rapport ;
- les schémas : XSpec vérifie un résultat attendu, jamais une validité.
Le chapitre valider et comparer du manuel décrit la comparaison d'arbres sur laquelle repose la vérification.