« Approuvé, la suite passe au vert » : ce commentaire de revue ne dit rien de ce qui a été vérifié dans les tests ajoutés eux-mêmes. Un test qui ne teste en réalité rien du tout peut se cacher derrière un statut vert, et personne ne s’en rend compte avant qu’une régression traverse une suite censée l’attraper, plusieurs mois plus tard. Un relecteur qui vérifie uniquement que la suite passe au vert, sans lire le contenu des tests ajoutés, laisse passer ce genre de problème sans le savoir.
Ce billet propose une grille de lecture pour repérer, en quelques minutes, un test qui ne teste rien, un mock trop permissif ou une assertion trop large ; il ne traite pas la revue du code de production, qui suit d’autres critères.
Vérification 1 : le test échoue-t-il vraiment sans le code testé ?
Le doute le plus fondamental à lever : ce test échouerait-il si le code qu’il est censé vérifier était supprimé ou cassé délibérément ? Un relecteur qui a un doute peut demander, en commentaire, de commenter temporairement la ligne clé du code de production pour confirmer que le test échoue bien dans ce cas.
public function test_remise_appliquee_apres_six_mois() {
$calculateur = new Fidelite_Calculateur();
$remise = $calculateur->calculer( 12, 100.00 );
$this->assertIsFloat( $remise );
// Cette assertion passe même si la méthode renvoie toujours 0.00 !
}
Ici, assertIsFloat vérifie uniquement le type retourné, jamais la valeur réelle attendue. Le test passe même si la logique de remise est entièrement cassée, tant que la méthode renvoie un nombre flottant quelconque.
Vérification 2 : le mock accepte-t-il n’importe quel argument ?
Un mock configuré sans contrainte sur ses arguments accepte n’importe quel appel, ce qui masque une erreur d’implémentation là où le mock aurait dû échouer :
// Trop permissif : accepte n'importe quel argument
WP_Mock::userFunction( 'wp_mail' );
// Explicite : échoue si l'appel ne correspond pas exactement
WP_Mock::userFunction( 'wp_mail', [
'args' => [ 'client@exemple-test.invalid', 'Confirmation de commande', Mockery::type( 'string' ) ],
'times' => 1,
] );

Vérification 3 : l’assertion est-elle assez précise ?
Une assertion large, comme assertNotEmpty sur un tableau attendu avec un contenu précis, laisse passer un résultat presque correct mais faux sur un détail important. Comparer une liste triée avec assertEqualsCanonicalizing, ou une structure complète avec assertSame, réduit ce risque par rapport à une vérification partielle du résultat.
| Assertion vague | Assertion précise équivalente |
|---|---|
| assertNotEmpty( $resultat ) | assertSame( $resultat_attendu, $resultat ) |
| assertTrue( is_array( $resultat ) ) | assertCount( 3, $resultat ) |
| assertIsString( $sortie ) | assertStringContainsString( ‘Confirmation’, $sortie ) |
Vérification 4 : le test a-t-il un nom qui décrit un seul comportement ?
Un test au nom générique, ou qui contient plusieurs assertions couvrant des comportements distincts sans lien logique entre eux, complique le diagnostic en cas d’échec futur. Un relecteur attentif demande une scission du test si son nom et son contenu ne correspondent manifestement pas à une seule intention claire.
La grille en une liste courte
- Ce test échouerait-il si le comportement testé était cassé délibérément ?
- Chaque mock impose-t-il une contrainte réelle sur ses arguments et le nombre d’appels attendus ?
- L’assertion finale vérifie-t-elle le contenu précis attendu, pas seulement sa présence ou son type ?
- Le nom du test correspond-il exactement à ce qui est vérifié dans son corps ?
Un relecteur qui n’a pas le temps de lire un test en détail devrait au minimum poser la question : « que se passe-t-il si je supprime ce code, ce test échoue-t-il vraiment ? »
En résumé
Une pull request qui ajoute des tests mérite une lecture aussi attentive que le code de production qu’elle prétend couvrir. Vérifier que le test échouerait sans le code testé, que les mocks imposent de vraies contraintes et que les assertions sont suffisamment précises permet de repérer, en quelques minutes, un test qui donne une fausse confiance plutôt qu’une réelle protection contre la régression.