Tests comme échantillons de contrôle
Présentation
Un test est un échantillon de contrôle : il fixe une attente pour que tout changement de comportement devienne visible. La discipline est : ne pas corriger le test pour qu'il corresponde au code ; comprendre pourquoi ils ne sont pas d'accord.
Les Règles
-
Test-first pour un changement comportemental. Écrivez le test qui échoue, regardez-le échouer pour la bonne raison, puis écrivez le code minimal qui passe. Si vous ne l'avez pas vu échouer, vous ne savez pas qu'il teste ce que vous pensez. La boucle elle-même est
superpowers:test-driven-development; ce skill régit ce que le test doit contenir et ce que signifie un échec une fois qu'il existe. La règle s'applique que ce skill soit installé ou non. -
Un changement structurel est vérifié par la suite inchangée. Un changement purement structurel — renommage, déplacement, division, extraction, sans changement de comportement observable — ne nécessite aucun nouveau test. Sa porte de sortie est l'inverse : toute la suite existante passe sans qu'aucun test ne soit modifié. C'est pourquoi le test-first ne s'applique pas ici — il n'y a aucun comportement à spécifier, donc aucun test ne peut échouer de manière significative en premier. Si un changement structurel force une modification de test, ce n'était pas structurel. Arrêtez-vous et reclassifiez en utilisant la règle suivante.
-
Un renommage qui traverse une frontière contractuelle n'est pas un refactoring. La question n'est pas « le code a-t-il changé de comportement ? » mais « ce nom est-il observable par quelque chose que je ne contrôle pas ? »
Élément renommé Classification Porte de sortie Symbole interne, helper privé, fichier local Structurel La suite existante passe sans modification Fonction exportée, champ API/GraphQL, route, indicateur CLI, colonne DB, nom d'événement, clé de configuration Comportemental — cela casse les consommateurs Test-first + ajout et dépréciation Un renommage public fait la même chose sur les mêmes données et reste un changement cassant, car le nom était le contrat (Loi de Hyrum — voir
performance-and-scale). Traitez-le comme comportemental : ajoutez le nouveau nom avec un cycle test-first qui échoue, conservez l'ancien nom et son test qui passe pendant la fenêtre de dépréciation, supprimez l'ancien uniquement après la migration des consommateurs.Le troisième cas est un test qui référence un nom sous forme de chaîne de caractères — snapshots, clés de conteneur DI, réflexion, chemins de fixtures. Ceux-ci cassent lors d'un renommage interne. Ce n'est pas un échec de la règle ; c'est le signal que le test était couplé à un détail d'implémentation. Corrigez le couplage, ne rebaselinez pas.
-
Les refactorings ne doivent pas modifier les tests ; les changements d'exigences, oui. Actes différents :
- Un refactoring casse un test → le test a détecté un changement de comportement non intentionnel. Arrêtez-vous et comprenez-le. Modifier le test pour faire passer un refactoring détruit votre instrumentation.
- Une exigence a véritablement changé → mettez à jour le test délibérément, comme un changement révisable distinct, enregistré comme tel.
- Le geste interdit est le rebaseline silencieux.
-
Couverture symétrique — chaque règle testée dans les deux directions. Prouvez que l'acteur autorisé peut et que l'acteur non autorisé ne peut pas. Les tests de chemin heureux ne testent qu'une moitié de règle.
-
Les règles de permissions/rôles sont une matrice : chaque cellule (rôle × action × ressource) significative, autorisation et refus. Un nouveau rôle est incomplet tant que ses cas de refus n'existent pas. Un test de refus qui passe soudainement est une régression de sécurité — révisez et acceptez explicitement, ne rebaselinez jamais.
-
Les points d'entrée ne sont jamais ajoutés/supprimés silencieusement. Routes, commandes, endpoints reçoivent chacun une couverture d'existence et de fumée, de sorte que l'ajout/suppression force une modification de test.
-
Test par taille : petit (logique pure, pas d'E/S, constant), moyen (base de données/processus réels, hermétique), grand (pile complète). Beaucoup de petits, moins de moyens, très peu de grands. Surveillez le chaînon manquant — la logique serveur/appli que ni les tests unitaires ni les tests d'intégration ne couvrent car chaque couche suppose que l'autre la teste.
-
Les tests instables sont mis en quarantaine sur-le-champ. Un test qui réussit/échoue sans changement de code détruit la confiance dans toute la suite. Mettez en quarantaine immédiatement (hors de la porte bloquante, déposez un correctif) — ne laissez jamais échouer par intermittence, ne supprimez jamais silencieusement. Les petits tests doivent être déterministes : pas d'horloge réelle, d'aléatoire ou de réseau.
-
Le code de test est DAMP, pas DRY. Descriptive And Meaningful Phrases (Phrases Descriptives et Significatives) plutôt que la déduplication. Un test doit être évident à lire isolément. Appliquer DRY aux tests est une violation, pas une vertu (voir
change-hygiene-and-code-craft).
Rationalisations Courantes
| Excuse | Réalité |
|---|---|
| « Je vais d'abord écrire le code, puis ajouter les tests » | Alors le test a été ajusté au code, pas à l'exigence. Vous ne l'avez jamais vu échouer, donc vous ne savez pas qu'il teste quelque chose. |
| « C'est juste un renommage, pas besoin de test » | Correct — si le nom est interne. S'il est exporté/public, le nom était le contrat et le renommage est comportemental. |
| « J'ai renommé et j'ai dû corriger trois tests, c'est normal » | Non. Soit vous avez traversé une frontière contractuelle, soit ces tests étaient couplés à un détail d'implémentation. Reclassifiez. |
| « Le test est maintenant erroné, je vais juste le mettre à jour » | Uniquement si une exigence a changé. Si vous refactorez, le test a détecté un changement réel — enquêtez. |
| « J'ai testé le chemin heureux, c'est suffisant » | Une règle n'est testée que lorsque le cas de refus l'est aussi. |
| « Ce test de refus passe maintenant, super, on livre » | Une action interdite qui réussit est une régression de sécurité, pas un progrès. |
| « Le test est instable, relancez simplement le CI » | L'instabilité masque de vrais bugs et entraîne tout le monde à ignorer les échecs. Mettez en quarantaine et corrigez. |
| « Laissez-moi DRYer ces tests avec un helper partagé » | Les tests sont DAMP. La duplication qui aide à la clarté sur place reste. |
Drapeaux Rouges — STOP
- Écrire du code de production pour un changement de comportement avant qu'un test échouant n'existe
- Un changement structurel dont le diff touche également les fichiers de test
- Renommer un nom public/exporté sur place au lieu d'ajouter et déprécier
- Modifier un test pendant un refactoring pour le faire passer au vert
- Une nouvelle permission sans test de refus
- Rebaseliner un snapshot/assertion sans comprendre pourquoi il a changé
- Un test avec un vrai
sleep,Date.now(), des données aléatoires ou un appel réseau - Refactorer le code de test pour supprimer la duplication au détriment de la lisibilité
Justification complète : Article IV de la constitution, regroupé dans engineering-constitution/references/engineering-constitution.md. Pour les spécificités des contrats GraphQL/route, voir graphql-contract-testing.