Tests de mutation en TypeScript : casser le code pour l'améliorer

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Êtes-vous sûr que vos tests fonctionnent vraiment ?

\n\n

Imaginez : vous avez 95% de couverture de code, tous les tests sont verts, le CI/CD passe sans erreur. L'équipe est satisfaite, le manager est serein. Mais un jour, un bug apparaît en production. Vous ouvrez le code, regardez les tests et comprenez : ils vérifient simplement que la fonction retourne un nombre, sans regarder sa valeur. Ou pire — le test est écrit de telle manière qu'il réussit dans tous les cas.

\n\n

Une situation familière ? Bienvenue dans le monde où le pourcentage de couverture n'est qu'un chiffre, pas une garantie de qualité. C'est là qu'intervient le test de mutation (mutation testing) — une méthode qui fait vraiment souffrir vos tests et les oblige à prouver leur valeur.

\n\n

Dans cet article, nous verrons comment intégrer le mutation testing dans un projet TypeScript en utilisant Stryker, et pourquoi cela pourrait devenir votre nouvel outil préféré (ou une nouvelle source de maux de tête).

\n\n

Qu'est-ce que le test de mutation en termes simples ?

\n\n

Le test de mutation, c'est quand vous prenez votre code, y introduisez délibérément de petites erreurs (mutations) et regardez si vos tests échouent. Si les tests ne tombent pas — vous testez mal. S'ils tombent — bravo, le mutant est éliminé.

\n\n

Ça semble sadique ? Peut-être. Mais ça marche. Imaginez que vous êtes un ingénieur QA qui s'est furtivement glissé dans le code et a remplacé === par ==, > par < ou a supprimé un appel de fonction. Un bon test doit le détecter. Un mauvais test le laissera passer.

\n\n

En TypeScript, avec son typage strict, le test de mutation est particulièrement utile : il détecte les cas où les types sont corrects, mais la logique ne l'est pas.

\n\n

Pourquoi la couverture de code classique (code coverage) est une illusion ?

\n\n

Supposons que vous ayez une fonction :

\n\n
function calculateDiscount(price: number, isPremium: boolean): number {\n  if (isPremium) {\n    return price * 0.8;\n  }\n  return price;\n}
\n\n

Et un test :

\n\n
test('returns number', () => {\n  const result = calculateDiscount(100, true);\n  expect(typeof result).toBe('number');\n});
\n\n

Couverture ? 100%. Test ? Réussi. Et si on remplace price * 0.8 par price * 0.5 ? Le test réussira quand même — il ne vérifie que le type, pas la valeur. Voilà un faux sentiment de sécurité.

\n\n

Le test de mutation mettra en évidence une telle mutation et dira : « Hé, ton test n'a pas remarqué que j'ai changé la réduction ! ». Et vous devrez ajouter une vérification correcte.

\n\n

Comment fonctionne le mutation testing en TypeScript : étape par étape

\n\n

L'ensemble du processus peut être divisé en trois étapes :

\n\n
    \n
  1. Génération de mutants — l'outil copie votre code et apporte une modification à la fois. Par exemple, il change && en || ou supprime un appel de méthode.
  2. \n
  3. Exécution des tests — pour chaque mutant, vos tests sont lancés.
  4. \n
  5. Analyse des résultats — si les tests échouent, le mutant est « tué ». Sinon, il a « survécu », et c'est une zone de risque.
  6. \n
\n\n

La métrique principale est le mutation score (pourcentage de mutants tués). 100% est l'idéal, mais en pratique, 80-90% est déjà excellent.

\n\n

L'outil n°1 pour TypeScript : Stryker

\n\n

Dans le monde de TypeScript et JavaScript, le roi du test de mutation est Stryker (StrykerJS). Il est rapide, fonctionne avec Jest, Vitest, Mocha et même avec les tests navigateur. Configuration en 5 minutes.

\n\n

Installation :

\n\n
npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
\n\n

Créons le fichier de configuration stryker.conf.js :

\n\n
// stryker.conf.js\nexport default {\n  mutate: ['src/**/*.ts', '!src/**/*.spec.ts'], // quoi muter\n  testRunner: 'jest',\n  reporters: ['html', 'clear-text', 'progress'],\n  jest: {\n    projectType: 'custom',\n    configFile: 'jest.config.ts',\n  },\n  concurrency: 4, // combien de mutants                

Blogs

Book Recommendations