Cassez votre code avant que la release ne s'en charge
Ce test a été livré en v2.2.0. Il ne teste rien du tout, et il faut lire la ligne deux fois pour comprendre pourquoi.
Ce test a été livré dans la v2.2.0 de akira-io/ui:
// src/blocks/login-form/index.test.ts
it('re-exports LoginFormStatusProps alongside the other part prop types', () => {
const props: LoginFormStatusProps = { message: 'ok' };
expect(props.message).toBe('ok');
});
Vitest transpile le TypeScript, il ne le vérifie pas. L’annotation disparaît avant l’exécution. Ce qui reste, c’est un objet portant une clé message à qui l’on demande s’il porte une clé message. Supprimez le re-export que ce test est censé protéger: il reste vert.
Il échoue sous tsc --noEmit. Rien n’exécutait tsc --noEmit.
Un test que vous n’avez jamais vu échouer n’est pas un test. C’est une ligne dans un rapport.
Onze propriétés, aucune vérifiée
J’ai réécrit onze propriétés déclarées dans src/blocks/login-form/parts.tsx pour que chacune ignore la valeur reçue et retombe sur sa valeur par défaut. La suite entière est passée.
Onze propriétés documentées, exportées, publiées. Deux d’entre elles, id et name, constituent la méthode documentée pour placer deux formulaires de connexion sur une même page. Aucune n’était exercée avec autre chose que sa valeur par défaut.
Le paramètre que personne ne lisait
Celui-ci aurait coûté de l’argent à d’autres que moi:
// scripts/release-dist-tag.mjs
export function resolveDistTag(version, currentLatest = '') {
const match = VERSION_PATTERN.exec(version);
if (!match) {
throw new Error(`${version} is not a valid semantic version`);
}
return match[4] ?? 'latest';
}
currentLatest est un paramètre. Le workflow de release interroge le registre pour l’obtenir et le transmet. La fonction ne le lit jamais. Toute version stable devient latest, sans condition.
Le scénario tient en trois phrases. Le paquet est en 2.1.0. Un correctif de sécurité part sur la ligne v1 et sort en v1.3.2. La publication l’étiquette latest, et chaque npm install, chaque @latest figé dans un Dockerfile, renvoie désormais une version v1 à des équipes qui tournent en v2.
Le workflow réussit. La page de release est correcte. Le seul symptôme se manifeste dans les builds des autres, et ce sont eux qui le découvrent avant vous.
L’objection évidente
Le mutation testing existe déjà comme catégorie. Stryker fait cela à grande échelle, génère les mutants, note la suite, et n’a besoin de personne pour choisir onze propriétés à la main.
C’est exact, et l’outil entrera dans le pipeline. Mais l’outil n’était pas la pièce manquante. Les propriétés étaient déclarées et le fichier se lisait en une minute. Ce qui manquait, c’était le refus de traiter une exécution verte comme une preuve. Aucun outil n’installe ce doute à votre place. Il démultiplie une méthode qui doit exister d’abord.
La version économique tient dans un après-midi. Prenez ce qu’un test prétend protéger, cassez-le, relancez le test. S’il passe, vous avez trouvé un trou. S’il échoue, vous avez trouvé un test.
Ce qui reste
La suite qui survivait aux onze mutations échoue désormais sur chacune. resolveDistTag lit le paramètre qu’il accepte, et un backport de majeure inférieure reçoit sa propre étiquette au lieu de celle que tout le monde installe par défaut. Un workflow exécute les tests, la vérification de types et le formatage sur chaque pull request, ce qui donne enfin un sens à une assertion que seul le compilateur peut évaluer.
Rien de tout cela n’est ingénieux. Tout cela existe parce que le vert a cessé de suffire.
Une suite verte ne prouve rien. Elle rassure, ce qui est exactement le problème.