Communitygithub.com

lguidolin/tests-as-a-control

À utiliser lors de l'écriture ou de la modification de tests, quand un test échoue pendant un refactoring, ou pour tester les règles de permissions/rôles. Symptômes : tentation de modifier un test pour le faire passer, tester uniquement le chemin heureux, un test de refus qui passe soudainement, tests instables, ou incertitude sur ce qu'il faut affirmer.

Qu'est-ce que tests-as-a-control ?

Ce skill incarne le principe selon lequel un test est un échantillon de contrôle : il fixe une attente pour rendre visible tout changement de comportement. La discipline centrale est de ne jamais modifier un test pour qu'il corresponde au code, mais de comprendre pourquoi ils divergent. Il distingue clairement les changements structurels (renommage, extraction) des changements comportementaux (API publique, routes), exigeant une approche test-first pour ces derniers. Il impose une couverture symétrique pour les règles de permission (autorisation ET refus) et traite tout test de refus qui passe soudainement comme une régression de sécurité. Les tests instables sont immédiatement mis en quarantaine, et le code de test doit suivre le principe DAMP (Descriptive And Meaningful Phrases) plutôt que DRY, privilégiant la clarté à la déduplication.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lguidolin/agent-skills/tree/main/skills/tests-as-a-control

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

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éClassificationPorte de sortie
    Symbole interne, helper privé, fichier localStructurelLa suite existante passe sans modification
    Fonction exportée, champ API/GraphQL, route, indicateur CLI, colonne DB, nom d'événement, clé de configurationComportemental — cela casse les consommateursTest-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

ExcuseRé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.

Individual skills in this repo

This repo contains 18 individual skills — each has its own dedicated page.

lguidolin/change-hygiene-and-code-craft

Use when writing or refactoring code, structuring a commit or PR, or deciding whether to abstract duplication. Symptoms — mixing reorg with logic changes, a PR doing several things at once, a file growing large, the second copy of similar code, or unsure whether to DRY something up.

lguidolin/cloud-delivery-aks

Use when deploying to Kubernetes or Azure Kubernetes Service (AKS), configuring cloud secrets, setting up progressive rollout/canary, per-PR ephemeral environments, or k8s health probes. Keywords — Kubernetes, AKS, Key Vault, Argo Rollouts, Flagger, canary, blue-green, liveness, readiness, PodDisruptionBudget, HPA, rollback, GHCR.

lguidolin/commit-history-rewrite

Use when an existing repository has messy commit history that needs to conform to conventional commits before adopting release-please, or when intermediate WIP/fixup/merge commits need to be cleaned up.

lguidolin/conventional-commits-and-releases

Use when committing, writing a commit message, opening a PR that will be squash-merged, or configuring automated versioning/changelogs. Keywords — conventional commits, release-please, semver, feat/fix/chore, breaking change, changelog.

lguidolin/defense-in-depth-security

Use when handling untrusted input, secrets, authentication/authorization, or dependencies — or threat-modeling a new surface. Keywords — STRIDE, threat model, least privilege, secrets management, supply chain, dependency scanning, input validation, audit log, defense in depth.

lguidolin/designing-before-building

Use when starting a feature, fixing a non-trivial bug, or about to write implementation code — before any code exists. Symptoms you need this: "this is simple, I'll just code it", reaching for the editor before a design is approved, or an idea that hasn't been turned into a spec and plan.

lguidolin/engineering-constitution

Use when starting work in a project that follows the engineering constitution, orienting to its rules, or deciding which engineering practice applies to a task — spec writing, commits, testing, security, deploys, database, or UI work.

lguidolin/graphql-contract-testing

Use when writing a GraphQL query/mutation that the UI and a test will share, or building route/schema contract or smoke tests. Symptoms — copying a query into a test, a test asserting on query text, schema change that didn't break the UI build, or RLS/permission drift. Keywords — graphql-codegen, typed document, contract test, route smoke test.

lguidolin/init-repo-CI

Use when setting up a new repository with conventional commits, release-please, and CI automation, or when retrofitting an existing repository that lacks automated versioning and PR validation workflows.

lguidolin/interface-craft-and-accessibility

Use when building or styling UI — components, layouts, forms, design tokens — or making accessibility decisions. Keywords — a11y, WCAG, keyboard navigation, focus state, contrast, design system, minimalist UI, component reuse, ARIA, semantic HTML.

lguidolin/merge-gates-and-automation

Use when setting up or changing CI, pre-push hooks, or a task runner, or deciding what must pass before merge. Symptoms — tempted to put authoritative checks only in a local hook, skip CI, bypass with --no-verify, or unsure what gates a merge vs. runs locally.

lguidolin/observability-and-slos

Use when adding logging, metrics, tracing, health checks, SLOs, or alerting — or when building a service surface that needs to be operable and debuggable. Keywords — structured logs, OpenTelemetry, correlation id, RED metrics, liveness, readiness, SLI, SLO, error budget, alerting.

lguidolin/performance-and-scale

Use when working on hot paths, list endpoints, pagination, data-access in loops, or public interfaces/schemas. Symptoms — unbounded queries, N+1 access, no latency budget, optimizing without measuring, or changing an interface many consumers depend on. Keywords — pagination, N+1, Hyrum's Law, performance budget, bundle size.

lguidolin/postgres-postgraphile-rls-and-sql

Use when writing PostgreSQL, PostGraphile config, Row-Level Security policies, SQL schema files, or working on the Browser→App→PostGraphile→Postgres data path. Keywords — RLS, SECURITY DEFINER, search_path, pgSettings, grants, roles, GraphQL depth limit, query cost, statement_timeout, SQL file organization.

lguidolin/recording-decisions

Use when a design or architecture decision has been made and needs to be captured — writing a decision record or ADR, updating a decision index, noting a deferred idea, or superseding a past decision. Keywords — ADR, decision record, rationale, rejected alternatives, dependency index.

lguidolin/resilience-and-deploy-safety

Use when planning a deploy, designing a rollback, or responding to an incident or writing a postmortem. Keywords — deploy safety, rollback, immutable artifact, progressive delivery, canary, blast radius, incident response, blameless postmortem, error budget.

lguidolin/ship-it

Use when the user wants to ship work — push, PR, archive decision records, merge, and clean up. Handles the full lifecycle from committing final changes through post-merge cleanup including converting specs/plans to compact decision records.

lguidolin/zero-downtime-migrations

Use when changing a database schema where data must survive the change — adding/removing/renaming columns, constraints, indexes, or backfilling. Symptoms — a destructive migration bundled with a code deploy, a NOT NULL column with a backfill, a table-locking UPDATE, or a rename. Keywords — expand/contract, parallel change, backfill, NOT VALID, CREATE INDEX CONCURRENTLY, graphile-migrate.

Skills associés