Tester les extraits de code dans un livre

| 3 min read

Pour ma formation sur l'amélioration des tests j'ai mis en place des fiches récapitulatives pour chacun des chapitres. Ces fiches récapitulatives contiennent pas mal de code et je voulais m'assurer que le code soit fonctionnel. Quoi de mieux que des tests pour s'assurer que le comportement est tel qu'attendu ? Plutôt logique dans une formation sur les tests.

Les fiches sont à destination des étudiants mais j'ai également écrit un livre blanc partangeant 5 manières d'améliorer la lisibilité et la maintenabilité des tests en utilisant cette même technique. Récupérez-le pour vous donner une idée du résultat.

Asciidoc et inclusion de code

Pour écrire les fiches, j'ai décidé d'utiliser Asciidoc, un format textuel présentant des similarités avec le Markdown, principalement pour sa capacité à inclure des fichiers à l'intérieur du texte, et plus particulièrement du code. Même mieux, il est possible de n'inclure qu'une partie d'un fichier, ce qui permet de ne montrer que le code utile à la compréhension.

Chaque chapitre est stocké dans un dossier différent et le fichier principal pour la fiche s'appelle fiche.adoc. Il y a également un dossier code dans ce même dossier qui va contenir les fichiers contenant le code.

Pour inclure du code, il est possible d'utiliser l'instruction asciidoc suivante:

[source,php]  
----  
include::code/code-01.php[tag=class-facture]  
----

Cette instruction va inclure la portion de code entre les tags tag::class-facture[] du fichier code-01.php.

Ce fichier ressemble peu ou prou à ça:

<?php  
  
namespace PasDeCalculDansLesTests\Code_01;

class UneClasseQuOnNeVeutPasMontrerMaisDontOnABesoin {

}

// tag::class-facture[]  
final class Facture  
{  
 // ...
}  
// end::class-facture[]  
  
// tag::class-produit[]  
final class Produit  
{  
}  
// end::class-produit[]  
  
// tag::testfile[]  
class FactureTest extends \PHPUnit\Framework\TestCase {  
  
	// tag::test_calcule_le_total[]
     /**
     * @test  
     */  
    public function calcule_le_total(): void {  
    }  
    // end::test_calcule_le_total[]  
}  
// end::testfile[]

Quelques informations importantes:

  • Comme vous le voyez, ce fichier contient tout le code qui lui est nécessaire afin d'être indépendant. C'est primordial car chaque chapitre du cours est présenté comme une histoire au travers de laquelle on va modifier le code ou les tests. On veut donc pouvoir avoir plusieurs versions du code en parallèle.
  • Chaque version du code est contenue dans son propre namespace, ici PasDeCalculDansLesTests\Code_01, ce qui permet d'éviter les conflits entre les différentes versions du code.
  • Le fichier de code contient le code qu'on ne souhaite pas montrer dans la fiche mais qui est pourtant nécessaire au bon fonctionnement du code.
  • Il est possible d'utiliser plusieurs tags différents pour montrer plusieurs sections du code. Le snippet précédent ne vas inclure que la classe Facture.
  • Les tests se trouvent également dans ce fichier, ce qui veut dire qu'ils sont aussi versionnés.

Lorsque l'histoire avance et qu'une autre version du code est nécessaire il suffit alors de copier le fichier et de créer le fichier code-02.php et de changer le namespace. On a alors un nouveau terrain de jeu indépendant.

Lancer les tests

Pour lancer les tests facilement, j'ai créé une suite de test PhpUnit qui permet de lancer les tests contenus dans n'importe quel fichier .php dans le dossier content, comme on le voit dans cet extrait du fichier phpunit.xml.

<testsuites>  
    <testsuite name="All">  
        <directory suffix=".php">./content/*</directory>
    </testsuite>
</testsuites>

Je peux donc désormais lancer les tests pour m'assurer que les exemples contenus dans les fiches sont corrects.

Exclure des tests

Dans certains chapitres, il est intéressant de montrer du code qui fait échouer les tests. Je ne veux pas que ma suite de test échoue pour ces tests à chaque lancement, j'ai donc fait en sorte de pouvoir les exclure facilement.

J'ai expérimenté avec deux idées.

La première est d'exclure les fichiers de code contenant les tests cassants de la suite de test, en utilisant la balise <exclude> de la configuration PhpUnit.

<testsuites>  
    <testsuite name="All">  
        <directory suffix=".php">./content/*</directory>
        <exclude>./content/sections/02-AAA/06-BBB/code/code-01.php</exclude>
    </testsuite>
</testsuites>

La seconde est d'utiliser une annotation @group disable sur les tests que je souhaitais pas lancer et de configurer PhpUnit, là encore dans phpunit.xml, pour exclure ces tests.

<groups>  
    <exclude>  
        <group>disable</group>  
    </exclude>  
</groups>

Il s'agit d'un des usages des tags dans ce projet. J'ai également utilisé les tags pour pouvoir aisément exclure les tests s'appuyant sur la base de données pour ne pas avoir besoin d'avoir un serveur disponible en permanence.

Avec ces idées, il est possible de facilement mettre en place des tests pour s'assurer que les exemples de code présent dans vos articles, livres, ou même documentations sont corrects.
D'ailleurs, inclure des fichiers de code ou des fichiers générés par du code permet de créer une documentation vivante, toujours à jour avec le fonctionnement actuel d’un système (en utilisant de l’approval testing comme l’explique Sébastien Fauvel.

Malheureusement, j’utilise du markdown

Si vous utilisez du Markdown, qui ne permet pas l’inclusion de fichier comme le fait Asciidoc, il est possible d’utiliser MarkdownSnippets. Ce programme va créer une copie du fichier markdown original en incluant le code, ce qui peut être fait dans un pipeline de build.

Je suis vraiment content du rendu des fiches dans la formation avec cette technique. Si vous avez besoin d’aide pour mettre en place ce genre d’idées dans vos équipes, parlons-nous).