Importance du test avec les IA
To me, legacy code is simply code without tests. (Michael Feathers, author of Dealing Effectively with Legacy Code)
To me, vibe coding is simply AI code without tests. (Moi. Et probablement beaucoup d’autres)
Sans test, pas d’évolution
Le test. Un vaste sujet qui fait peur à beaucoup, qui crée des allergies chez d’autres. Et mon cheval de bataille de ces 15 dernières années.
Le test est central dans l’ingénierie logicielle. Je ne vais pas pointer les milliers d’articles qui en parlent. Je me contenterai de la genèse, avec la présentation des tests dans Extreme Programing.
Sans tests automatiques, vous n’avez aucune garantie de non-régression. Qu’est-ce que ça implique ?
- Vous devez retester une large portion de l’application à chaque évolution
- Vous n’osez plus modifier le code en profondeur : vous mettez des rustines. Les rustines s’accumulent, le code devient difficile à maintenir
- “Il faudrait réécrire” devient un doux rêve. Vous ne pouvez pas toucher le code sans risquer de casser quelque chose.
En 15 ans, j’ai vu la différence de trajectoire entre les projets qui avaient des tests et ceux qui n’en avaient pas. On ne le voit pas sur les premières semaines. Non, les symptômes apparaissent après… juste quand on a assez de code pour que mettre en place le framework de test devienne une plaie.
C’est la Design Stamina Hypothesis mise en avant par Martin Fowler qui vous tombe en plein dessus.
Avec l’IA ? C’est pire.
Vous connaissez cette petite sueur froide quand il faut toucher une partie complexe de l’application et que vous savez pertinemment qu’elle n’est pas testée ? Ce moment où vous contemplez votre existence en vous demandant si vous allez oser entrer dans ce code ? Où vous réfléchissez à chaque caractère que vous tapez, à chaque ligne que vous déplacez ?
L’IA, elle ?
La différence entre vous et l’IA ? L’IA n’est pas responsable. Elle modifie le code partout où elle pense avoir besoin. À cause d’un mot de travers dans votre prompt, elle pourrait même faire du zèle. Et après ? Elle vous dira juste: “Oh. Pardon”.
Vous êtes responsable. C’est à vous qu’il revient de vous assurer que le logiciel marche, et continue de marcher. C’est votre travail. Pas celui de l’IA !
L’importance de la stratégie de tests
La limite des tests unitaires
Bon, maintenant, imaginons que je vous ai convaincu. Vous vous dites : “C’est l’heure de tester unitairement chacune des fonctions de cette base de code”.
Sauf qu’il y a un problème. Si vous découvrez que vous avez la mauvaise architecture, qu’est-ce qui va arriver à vos tests quand vous vous réécrire une partie de votre module ?
Ils ne vont servir à rien. Pire, ils vont se mettre dans le chemin. Si vous devez refactorer 5000 lignes de test en plus des 2500 lignes de code, vous croyez que vous allez le faire ? Non.
Et ça, je ne suis pas le seul à le penser. C’est d’ailleurs le message que nous fait passer Jonathan Boccara au Devoxx 2022 (vraiment, allez voir cette vidéo !).
En une phrase : les tests figent le design.
Et si l’IA le fait ? Si l’IA modifie le code, puis, dans la foulée, ajuste les tests pour qu’ils passent, à quel moment est-ce que vous savez si vous avez régressé ou non ?
Conclusion : il nous faut un cadre de test dans lequel l’IA évolue.
L’avènement de tests efficients
Bonne nouvelle : depuis les premiers tests “unitaires”, on a gagné un peu de puissance de calcul et de RAM. Avec les frameworks modernes, on peut faire des tests d’intégration ou des tests E2E.
Pensez :
- Aux outils de test E2E comme Cypress ou Playwright, qui peuvent tester :
- Votre front + votre back ensembles
- Ou juste votre front (si vous bouchonnez les APIs !)
- À tous vos frameworks qui ont leur propre framework de test d’intégration :
Au passage, c’est là que l’inversion de dépendance et la modularisation ont leur force, mais c’est une histoire pour un autre post.
Avec ces tests, l’IA peut réécrire le code sans toucher les tests à l’échelle d’un module complet. Si vous voyez que votre IA a touché des tests existants, il ne vous reste plus qu’à aller voir lesquels, et est-ce que c’est normal.
Un exemple concret
Le Jardin des Esperluettes est développé en PHP presque pur (avec un tout petit peu d’AlpineJS pour l’interactivité).
Ma stratégie :
- une majorité de tests d’intégration
- où je remplace Mysql par Sqlite (transparent avec Eloquent)
- avec quelques helpers exportés par chaque module (par exemple pour créer des utilisateurs à la volée)
- je teste principalement le HTML, quelques fois le
ViewModelquand j’ai besoin de précision - je teste les
PublicApi(les classes que mes modules exposent à destination des autres modules), pour vérifier leur fonctionnement
- je complète avec quelques tests unitaires sur des parties ultra spécifiques :
- sur des composants isolés réutilisés partout
- sur des classes de config
Le résultat :
Certains diront que 0.13s par test, c’est beaucoup. Et en même temps, pour tester toute la logique de la page, depuis l’appel au contrôleur jusqu’au rendu HTML, c’est pas mal. Mes tests sont répartis dans des modules, je peux ne lancer qu’un certain module. Le plus gros module (celui avec les histoires) contient 371 tests et met 48 secondes à s’exécuter. C’est acceptable.
La stratégie de test est un arbitrage
Vous ne pouvez pas avoir le beurre et l’argent du beurre :
- Plus vous faites des tests à large spectre, plus ceux-ci seront longs
- Mais plus ils amèneront un feedback important
- À l’inverse, des tests sur des modules plus petits (jusqu’au test unitaire) donneront des feedbacks plus précis, mais plus rapides.
En réfléchissant dès le départ à une stratégie de test, vous pouvez ajuster votre architecture pour trouver le meilleur compromis entre vitesse et flexibilité. Et vous pouvez créer un cadre pour votre IA : “vas-y, fais-toi plaisir, je contrôle”
Tests générés par IA ?
Le bonus : vous pouvez faire générer les tests par IA. Aujourd’hui, Brice génère la majorité des tests du Jardin des Esperluettes.
Mais voici quelques conseils :
- Vous devez vérifier les tests de la nouvelle fonctionnalité : qu’ils vérifient toutes les règles, et qu’ils font ce qu’ils disent.
- Vous devez vérifier qu’un test est ajouté chaque fois que vous trouvez un bug. Idéalement avant de corriger (oh, tiens… du TDD/BDD ?)
- Si l’IA touche des tests existants, vous devez vérifier la pertinence de la modification.
Conclusion
À l’heure où les IAs écrivent la majorité de nos bases de code, la meilleure façon de ne pas leur céder le contrôle (et la responsabilité, dont elles se moquent éperdument) est de mettre en place un framework de test solide.
C’était déjà un gage de qualité avant. Ça l’est d’autant plus maintenant. C’est un processus qui se pense au plus tôt. Et l’IA peut vous assister dans le process.
So there’s nothing to stop us from testing for the rest of your life. 😉 (GladOS, Portal 2)