strengthen-tests

Par n8n-io · n8n

Prendre un fichier `summary.json` Stryker (issu de `n8n:mutation-test`), trier les mutants survivants par risque sur le comportement observable par l'utilisateur, écrire des modifications d'assertions minimales pour éliminer les 3 à 5 survivants les plus critiques, puis vérifier en relançant `n8n:mutation-test`. À utiliser lorsque l'utilisateur vient d'exécuter des tests de mutation et souhaite renforcer la suite de tests, ou dit « kill the survivors / strengthen tests / fix the red ». S'associe à `n8n:mutation-test` comme la partie écriture interne d'une seule itération.

npx skills add https://github.com/n8n-io/n8n --skill strengthen-tests

Renforcer les tests — éliminer les survivants les plus importants

L'autre moitié de la boucle locale de mutation testing. n8n:mutation-test rapporte les mutations qui ont échappé aux tests ; cette skill sélectionne celles qui comptent et rédige des changements d'assertion minimaux pour les éliminer.

Quand l'utiliser

  • L'utilisateur vient de lancer /n8n:mutation-test <file> et le verdict est red
  • L'utilisateur dit : « renforcer les tests », « éliminer les survivants », « réparer le red », « itérer sur les tests pour X »
  • En cours de boucle : l'étape de vérification de cette skill appelle n8n:mutation-test à nouveau, la boucle se ferme ici

Ne pas utiliser cette skill :

  • Avant que du mutation testing ait été exécuté sur le fichier cible (pas de summary.json à trier)
  • Pour un verdict green — il n'y a rien à renforcer ; si l'utilisateur insiste, repousser et demander quel fichier a vraiment besoin de travail
  • Pour éliminer en masse chaque survivant — explicitement limité à 5 par invocation. Réinvoquer pour plus.

Entrées

  • Par défaut : lire packages/workflow/reports/mutation/summary.json (sortie de la dernière exécution de n8n:mutation-test).
  • Substitution : --summary <path> pour pointer vers un autre fichier de résumé.

Étapes

1. Lire le résumé

packages/workflow/reports/mutation/summary.json. Déjà compact (~50 KB). Extraire :

  • files[0].file — le fichier source testé
  • files[0].score — score de mutation courant
  • files[0].survivors[] — chaque mutant survivant (et sans couverture) avec localisation, remplacement, noms des tests couvrants

Si summary.json est manquant, arrêter. Dire à l'utilisateur de lancer n8n:mutation-test en premier.

2. Lire la source testée, sobrement

Lire le fichier source référencé dans summary.json. Lire une fois, le fichier entier (les fichiers source n8n-workflow typiques font 50-500 lignes ; le coût est borné). C'est le seul fichier lu ; ne pas charger les fichiers de test encore.

3. Trier les survivants

Catégoriser chaque survivant dans l'un de trois buckets. Utiliser le rubrique ci-dessous — ne pas l'appliquer mécaniquement, mais s'y appuyer.

HAUTE importance — « vecteur de régression réel » : mutant qui protège un comportement que les vrais utilisateurs atteignent réellement par la surface API publique.

  • Vérifications de type contre les formes que les données utilisateur prennent routinement : null, undefined, Date, Buffer, Uint8Array, Array, objets simples avec clés arbitraires
  • Branches conditionnelles qui gardent un parcours critique (p. ex. « si cela retourne tôt, le mauvais parcours exécute »)
  • Chemins de code qui gèrent le flux contrôlé par l'utilisateur : expressions, comportement Code-node, manipulation d'items binaires, sémantique deep-clone
  • Mutants sur clauses de garde qui préviennent des crash (if (value == null) return ...)

IMPORTANCE MODÉRÉE — « invariant observable par l'utilisateur » : réel mais impact utilisateur moins fréquent.

  • Pièges proxy qui changent la sémantique Object.keys / in / hasOwnProperty après mutation
  • Séquences set-puis-delete / re-set-après-delete (motifs d'assignation Code-node)
  • Traiter l'assignation undefined comme suppression (obj.foo = undefined → clé supprimée)
  • Cas limites qui ne se déclenchent que sous des motifs d'itération spécifiques

BASSE importance — « assurance refactoring » ou bruit : ignorer ceux-ci. Documenter le saut dans la sortie mais ne pas rédiger de tests.

  • Vérifications de propriété constructeur (arr.constructor === Array) sauf si le code production en dépend
  • Invariants d'idempotence déclenchés uniquement par d'autre code de bibliothèque (Lodash, spread natif)
  • Mutants équivalents — mutations qui produisent du code sémantiquement identique (p. ex. inverser un conditionnel inutilisé)
  • Mutants sur helpers internes que les utilisateurs ne peuvent pas atteindre via aucune API publique

Si le résumé énumère des survivants noCoverage, les traiter comme leur propre bucket : « la suite de test n'exécute même pas ces lignes ». Les trier par la même rubrique, mais flaguer séparément car ils ont besoin d'un cas de test nouveau plutôt qu'une extension d'assertion.

4. Choisir l'ensemble de travail : jusqu'à 5 mutants

Ordre : tous les HIGH d'abord, puis MODERATE s'il reste du budget, jamais LOW. Plafond dur à 5 au total. Si HIGH seul dépasse 5, choisir les 5 plus distincts (ne pas choisir 5 mutants sur la même ligne — ils partagent probablement un fix ; choisir des représentants dans le fichier).

Si moins de 3 candidats HIGH+MODERATE existent, faire ce qui est là. Ne pas remplir avec LOW juste pour atteindre un nombre.

Rédiger l'ensemble de travail à l'utilisateur avant d'éditer :

Picked N survivors to address (M skipped as refactor-insurance / low-leverage):
  1. [HIGH] location — original → replacement
     plan: assert <X> in <covering test name>
  2. [HIGH] ...
  3. [MODERATE] ...
  ...

C'est la chance pour l'utilisateur de rediriger. Ne pas rédiger de code encore.

5. Lire les tests couvrants pour les survivants choisis

Pour chaque survivant choisi, le résumé énumère les noms de test qui ont couvert la ligne. Trouver ces tests dans le fichier de test (habituellement packages/<pkg>/test/<source-basename>.test.ts, mais vérifier) et lire juste les blocs test('...') pertinents — pas le fichier entier. Utiliser Grep + Read avec décalages de ligne pour garder le coût en tokens bas.

Objectif : comprendre ce que l'assertion existante asserte pour que la nouvelle assertion soit additive, pas contradictoire.

6. Rédiger les changements

Contraintes :

  • Préférer étendre un test couvrant existant plutôt qu'en ajouter un nouveau. Moins de churn de fichier, révision plus facile.
  • Matcher le style existant — même bibliothèque d'assertion, mêmes idiomes matcher (.toBe vs .toEqual vs .toStrictEqual).
  • Additions minimales — une ou deux assertions par mutant, pas un it-block nouveau par mutant.
  • Pas de fabrication — seulement asserte ce que le code source fait réellement. Si vous ne pouvez pas le dire depuis la source, arrêter et demander à l'utilisateur.
  • Pour les survivants noCoverage, ajouter un nouveau cas de test nommé d'après le comportement épinglé. Placer à côté des tests connexes.

Utiliser Edit avec correspondances de chaîne exacte. Ne jamais réécrire des fichiers de test entiers.

7. Vérifier

Réinvoquer n8n:mutation-test sur le même fichier source. Rapporter :

Before:  red 76.74% (28 survivors)
After:   green 82.34% (22 survivors)
Killed:  6 of 5 targeted (1 bonus — fix for #77 also killed #78)
Still surviving: 22 — re-invoke /n8n:strengthen-tests for another batch.

Si le score a monté mais le seuil n'est toujours pas atteint : l'itération fonctionne, recommander un autre passage. Si le score a baissé ou est resté le même : au moins un test nouveau n'asserte pas ce qu'on pense. Exposer le diff à l'utilisateur et arrêter — ne pas auto-annuler. Si un test échoue maintenant (ne survit pas — échoue réellement) : nous avons asserété quelque chose que le code ne fait pas. Annuler cette assertion spécifique, laisser le reste, rapporter laquelle était fausse.

Forme de sortie

Chaque invocation produit :

  1. Plan de travail (avant les édits) — les survivants choisis et le plan pour chacun
  2. Diffs (pendant les édits) — appels outil Edit, visibles dans la transcription
  3. Vérifier (après) — relancer + comparaison avant/après

Garder la prose minimale entre sections. Le plan et les étapes de vérification sont les sorties structurées ; tout le reste est mécanique.

Contraintes

  • 5 mutants max par invocation. Réinvoquer pour plus. Prévient les sessions qui s'emballent sur fichiers à 30 survivants.
  • Ne jamais fabriquer d'assertions. Si la source ne fait clairement pas X, ne pas le prétendre.
  • Pas de fichiers de test nouveaux sauf absolument nécessaire. Étendre le fichier de test couvrant existant.
  • Pas d'annulation des tests d'autres personnes. Éditer seulement les tests du package muté.
  • Ne pas relancer mutation-test plus qu'une fois par invocation. C'est l'étape de vérification. Ne pas boucler dans une seule invocation ; laisser l'utilisateur réinvoquer.
  • Pas de commits. Les édits atterrissent dans l'arbre de travail ; l'utilisateur révise et commite.

Suites courantes

  • L'utilisateur dit « go again » → réinvoquer cette skill. Le summary.json reflète maintenant l'état post-édition.
  • L'utilisateur dit « pourquoi #N a été classé en LOW ? » → expliquer l'application de la rubrique pour ce mutant spécifique, pas de retri des autres.
  • L'utilisateur dit « tue #N spécifiquement » → substituer la triage pour ce mutant, le traiter comme choisi.
  • L'utilisateur dit « saute l'étape de vérification » → non ; l'étape de vérification est le contrat que les édits ont réellement fait bouger le score.

Connexes

  • n8n:mutation-test — le côté lecture de cette boucle
  • scripts/mutation-health/README.md — l'histoire d'observabilité sauvegardée par BQ dans laquelle cela s'insère

Skills similaires