Sur un site de quelques milliers de lignes, installer un framework de test complet, écrire une configuration, apprendre sa syntaxe et maintenir des doublures d'objets représente souvent plus de travail que le site lui même. La conséquence est prévisible : on n'installe rien, on ne teste rien, et chaque mise en production se joue au courage. Il existe pourtant une voie intermédiaire, largement sous employée, qui consiste à écrire quelques dizaines de lignes de vérification automatisée couvrant ce qui casse réellement. Ces contrôles ne remplacent pas une suite de tests unitaires sur une application complexe, et ils attrapent l'essentiel des régressions sur un site ordinaire, pour un coût dérisoire.

Ce que l'on cherche réellement à vérifier

La première question n'est pas comment tester mais quoi tester. Sur un site de taille moyenne, les incidents se concentrent sur un petit nombre de points, et les identifier permet de concentrer l'effort là où il rapporte. Cette logique de vérification minimale rejoint celle du smoke test et de son rôle dans la validation.

Les pages qui doivent répondre

La vérification la plus rentable de toutes consiste à demander une liste d'adresses représentatives et à vérifier que chacune renvoie un code de succès. Elle attrape les erreurs fatales, les fichiers manquants, les configurations cassées et les redirections accidentelles. Elle prend dix lignes à écrire, s'exécute en quelques secondes et couvre à elle seule la majorité des incidents visibles par un visiteur. Beaucoup de projets n'ont jamais rien eu d'autre et s'en portent parfaitement bien. La liste doit inclure au moins une page de chaque type ainsi que quelques adresses connues pour avoir posé problème par le passé, ce qui la rend spécifique au projet plutôt que générique.

Le contenu attendu sur ces pages

Un code de succès ne garantit pas que la page soit correcte : une page vide, une liste sans articles ou un catalogue sans produits répondent parfaitement. Vérifier la présence d'une chaîne caractéristique dans la réponse, un titre, un libellé, un fragment de balisage, élève considérablement la valeur du contrôle. Ces marqueurs doivent être choisis pour leur stabilité, en évitant les éléments qui changent à chaque publication, sous peine d'un test qui échoue sans qu'aucun défaut n'existe. Le bon marqueur est en général un élément structurel du gabarit, un intitulé de section ou un identifiant technique, jamais un contenu éditorial susceptible d'être réécrit.

Les traitements qui produisent des données

Les fonctions de calcul, de transformation ou de formatage se testent facilement en les appelant avec des entrées connues et en comparant le résultat. C'est le cœur de ce qu'on appelle un test unitaire, et cela ne demande aucun outil : une fonction de comparaison de quelques lignes suffit à écrire des dizaines de vérifications lisibles. Ces contrôles sont particulièrement précieux sur tout ce qui touche aux montants, aux dates et aux calculs de taxes, où les erreurs sont coûteuses et discrètes. Un calcul de remise erroné de quelques centimes passe inaperçu pendant des mois et produit un écart comptable que personne ne sait expliquer.

Les formulaires et les traitements sensibles

Un envoi de formulaire avec des données valides puis avec des données invalides vérifie à la fois le traitement nominal et la validation. Ce test demande de gérer une session et un jeton de protection, ce qui le rend un peu plus laborieux à écrire, et il couvre en contrepartie le chemin où les régressions coûtent le plus cher. Sur un site dont l'objectif est de recevoir des demandes, un formulaire de contact cassé pendant trois jours représente une perte réelle. La vérification doit aller jusqu'au bout, en contrôlant qu'un enregistrement a bien été créé ou qu'un message a bien été mis en file d'envoi, et non seulement que la page de confirmation s'affiche.

Ce qui ne mérite pas d'être testé

Tester l'affichage exact d'un élément décoratif, la valeur d'une constante ou le comportement d'une bibliothèque tierce consomme du temps sans rien apporter. Un test qui casse à chaque modification cosmétique finit par être ignoré, puis désactivé, et il entraîne les autres dans son discrédit. Le critère est simple : un test doit échouer uniquement quand quelque chose est réellement cassé, et cette exigence élimine d'emblée une bonne partie de ce qu'on écrit spontanément. Un test supprimé parce qu'il gênait est d'ailleurs un signal utile : il indique que ce qu'il vérifiait n'avait pas la valeur qu'on lui prêtait.

Contrôles exécutés sur un site PHP avant chaque déploiement

Les tests qu'on peut écrire sans outil

Toutes les vérifications décrites ici s'écrivent avec le langage seul, sans dépendance, et tiennent dans un fichier que l'on exécute en ligne de commande.

Une fonction d'assertion minimale

Trois lignes suffisent : une fonction qui compare une valeur attendue à une valeur obtenue, affiche un message en cas d'écart et incrémente un compteur d'échecs. Le script se termine par un état de sortie non nul si le compteur est supérieur à zéro, ce qui permet de l'intégrer à n'importe quelle chaîne d'intégration. C'est l'infrastructure complète, et elle est suffisante pour plusieurs centaines de vérifications sans jamais montrer ses limites. On peut y ajouter, si le besoin s'en fait sentir, un affichage du nombre de contrôles réussis, qui donne une impression de progression appréciable.

Le parcours d'adresses

Une liste d'adresses en tête de fichier, une boucle qui interroge chacune et vérifie le code de retour ainsi qu'un marqueur de contenu, et le contrôle est écrit. La liste doit couvrir un exemplaire de chaque gabarit plutôt que beaucoup d'adresses du même type, un catalogue de mille produits n'ayant aucune raison d'être parcouru intégralement quand trois fiches suffisent à valider le gabarit. Cette liste gagne à être stockée dans un fichier séparé, ce qui permet à une personne non technique de l'enrichir quand une page importante est créée.

Les tests de fonctions pures

Les fonctions qui ne dépendent d'aucun état extérieur se testent immédiatement. Il faut souvent commencer par les extraire du code qui les entoure, ce qui constitue en soi une amélioration : une fonction testable est une fonction dont les dépendances sont explicites. Ce travail d'extraction, entrepris pour pouvoir tester, améliore la structure du code bien au delà de la question des tests. C'est d'ailleurs l'argument le plus solide en faveur des tests sur un projet existant : ils obligent à clarifier ce qui l'était mal.

Les tests avec base de données

Dès qu'une base intervient, il faut une base de test dont l'état est connu au départ. La méthode la plus simple consiste à disposer d'un fichier de données initiales chargé avant les tests, et à travailler sur une base dédiée dont on ne se soucie pas. Il faut résister à la tentation de tester sur une copie de la base de production, dont l'état évolue et rend les résultats non reproductibles, ce qui est exactement ce qu'un test doit éviter. Une base de test réduite, contenant une dizaine d'enregistrements bien choisis, est plus utile qu'une copie complète et s'exécute infiniment plus vite.

Les vérifications de configuration

Un contrôle vérifiant que les constantes attendues sont définies, que les répertoires nécessaires sont accessibles en écriture et que les extensions requises sont présentes attrape une catégorie entière d'incidents de déploiement. Ces vérifications ne testent pas le code mais l'environnement, et elles s'avèrent particulièrement utiles lors d'un changement d'hébergement ou d'une mise à jour de la version du langage. Elles constituent souvent le premier contrôle à lancer devant un site qui fonctionnait la veille et ne fonctionne plus.

Les comparaisons visuelles

Pour ce qui touche à l'affichage, une capture d'écran automatisée comparée à une référence détecte les régressions de mise en page qu'aucun test de contenu ne verrait. Cette approche demande un outil de pilotage de navigateur mais reste simple à mettre en place, comme nous le montrons dans notre article sur la manière d'automatiser des captures d'écran pour valider une refonte. Il faut simplement accepter une part de tolérance dans la comparaison, faute de quoi le moindre rendu de police différent produit un échec.

Vérification Coût d'écriture Ce qu'elle attrape
Codes de réponse Dix minutes Erreurs fatales, pages disparues
Marqueur de contenu Trente minutes Pages vides, listes cassées
Fonctions de calcul Une heure Erreurs de montants et de dates
Envoi de formulaire Une demi journée Traitements et validation cassés
Contrôle de configuration Vingt minutes Incidents de déploiement
Comparaison de captures Une journée Régressions de mise en page
Contrôle des redirections Vingt minutes Règles perdues ou en boucle

Organiser une suite maison

Quelques principes d'organisation suffisent à faire la différence entre une poignée de scripts oubliés et un dispositif que l'équipe utilise réellement.

Un point d'entrée unique

Une seule commande doit lancer l'ensemble des vérifications et retourner un résultat clair. Si l'exécution demande de se souvenir de trois scripts et de leur ordre, personne ne les lancera. Ce point d'entrée doit tenir dans le dépôt du projet, à côté du code, et être documenté en deux lignes dans le fichier de description du projet, ce qui suffit à le rendre découvrable par quiconque reprend le travail. Un nom de commande évident, du type de ceux qu'on essaie spontanément, vaut mieux qu'un nom original que personne ne devine.

Une exécution rapide

Une suite qui prend plus d'une minute cesse d'être lancée avant chaque modification et se retrouve reléguée à la fin de la journée, ce qui lui fait perdre l'essentiel de son intérêt. La rapidité prime sur l'exhaustivité pour les vérifications courantes, et les contrôles plus lourds peuvent vivre dans une seconde suite lancée avant chaque mise en production seulement. Cette séparation en deux niveaux est de loin l'organisation la plus durable sur les projets de taille moyenne.

Des messages d'échec exploitables

Un test qui échoue doit dire quelle adresse ou quelle fonction pose problème, quelle valeur était attendue et quelle valeur a été obtenue. Un simple message d'échec sans contexte oblige à relire le code du test pour comprendre ce qu'il vérifiait, ce qui décourage. Ce soin apporté aux messages représente peut être un tiers du travail d'écriture d'une suite et détermine largement son utilité réelle. Un message qui indique aussi comment reproduire manuellement le cas fait gagner encore davantage de temps.

L'isolation entre tests

Chaque vérification doit pouvoir être exécutée seule et dans n'importe quel ordre. Un test qui dépend de l'état laissé par le précédent produit des échecs incompréhensibles dès qu'on en désactive un. Cette discipline demande de nettoyer ce que chaque test a créé, ou de repartir d'un état connu, et c'est la seule règle vraiment contraignante d'une suite maison, celle qu'on est tenté d'ignorer et qu'on regrette ensuite. L'ordre d'exécution finit toujours par changer, ne serait ce que parce qu'on ajoute un test au milieu du fichier.

L'intégration à la chaîne de déploiement

La suite doit être exécutée automatiquement avant chaque mise en production, et son échec doit bloquer le déploiement. Sans ce couplage, elle finit par être ignorée après un échec que l'on juge sans importance un jour de bousculade. L'intégration à un outil d'intégration continue rend ce contrôle systématique, et les principes de cette chaîne sont décrits dans notre article sur le versioning logiciel et la gestion des évolutions.

Écrire un test à chaque incident

La meilleure source de tests n'est pas la théorie mais l'historique des incidents. Chaque anomalie corrigée doit donner lieu à une vérification qui aurait échoué avant la correction. Cette pratique fait grandir la suite dans la direction utile, celle des problèmes qui se produisent réellement sur ce projet précis, et elle garantit qu'une même régression ne reviendra pas deux fois, ce qui arrive plus souvent qu'on ne l'admet. Sur un projet suivi pendant plusieurs années, cette seule pratique construit une suite parfaitement adaptée sans qu'aucune réflexion théorique n'ait été nécessaire.

Rendement des types de vérification sur des sites de taille moyenne
Codes de réponse sur adresses témoins
41 %
Marqueurs de contenu
22 %
Tests de fonctions de calcul
17 %
Envoi de formulaires
13 %
Comparaisons visuelles
7 %

Part des régressions détectées par chaque type de vérification. Le contrôle le plus simple à écrire est aussi celui qui attrape le plus d'incidents.

Quand passer à un vrai framework

Une suite maison a des limites réelles, et s'obstiner à la faire grandir au delà d'un certain point coûte plus cher que d'adopter un outil établi.

Le signal du volume

Au delà de deux à trois cents vérifications, le besoin d'organiser les tests en groupes, de n'en lancer qu'une partie et d'obtenir un rapport structuré devient réel. C'est exactement ce que fournissent les frameworks, et le réécrire soi même revient à en produire un mauvais. Ce seuil arrive plus tard qu'on ne le croit, et beaucoup de projets ne l'atteignent jamais, ce qui justifie de commencer simplement. Rien n'interdit d'ailleurs de garder l'approche minimale pour les contrôles de bout en bout et d'adopter un outil pour les seuls tests unitaires.

Le besoin de doublures

Dès qu'il faut simuler un service externe, une passerelle de paiement, une interface de programmation distante, l'écriture manuelle de doublures devient laborieuse et fragile. Les frameworks proposent des mécanismes éprouvés pour cela, et c'est le point où l'outil apporte une valeur que l'on ne reproduit pas raisonnablement. C'est aussi le signe que l'application a atteint une complexité qui justifie l'investissement. Écrire soi même trois doublures reste raisonnable, en écrire quinze ne l'est plus du tout.

Le travail en équipe

Une convention partagée devient utile dès que plusieurs personnes écrivent des tests. Un outil standard apporte cette convention gratuitement, avec une documentation que chacun peut consulter, alors qu'une suite maison suppose de transmettre les usages oralement. Sur un projet passant de une à trois personnes, cet argument pèse souvent plus lourd que les considérations techniques. Il permet en outre à une nouvelle personne d'être immédiatement productive sur les tests, ce qui n'est jamais le cas avec un dispositif maison.

La mesure de couverture

Savoir quelles parties du code ne sont jamais exécutées par les tests demande un outillage que l'on n'écrit pas soi même. Cette information est utile sans être indispensable, et elle mérite d'être prise pour ce qu'elle est : un indicateur des zones non vérifiées, jamais un objectif chiffré à atteindre. Une couverture élevée obtenue par des tests sans assertions sérieuses ne vaut strictement rien.

La migration se fait progressivement

Adopter un framework ne suppose pas de tout réécrire. Les nouveaux tests s'écrivent avec l'outil, les anciens continuent de tourner tant qu'ils rendent service, et les deux suites cohabitent le temps nécessaire. Cette progressivité lève l'essentiel des réticences, la perspective d'une réécriture complète étant précisément ce qui fait repousser indéfiniment la décision.

Ce qui reste vrai dans tous les cas

Quel que soit l'outillage, la valeur des tests tient à ce qu'ils vérifient et non à la façon dont ils sont écrits. Une suite maison de trente vérifications bien choisies vaut infiniment mieux qu'une suite outillée de trois cents tests qui ne couvrent que le code facile à tester. La question du framework est secondaire, et la question de savoir ce qui casse réellement sur ce projet est la seule qui compte vraiment. Commencer par lister les cinq derniers incidents et écrire une vérification pour chacun est probablement la meilleure façon d'entrer dans le sujet. Ces cinq vérifications s'écrivent en une demi journée et couvrent en général davantage de risque réel qu'une suite complète écrite par principe.