Tests basés sur les propriétés en TypeScript : comment détecter des bugs que vous ne cherchiez pas

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

Introduction : pourquoi les tests unitaires ne suffisent pas à tout

\n\n

Imaginez que vous ayez écrit une fonction de tri de tableau. Vous l'avez couverte d'une dizaine de tests unitaires : tableau vide, un seul élément, liste déjà triée, ordre inverse. Les tests sont verts. Vous avez confiance en votre code. Mais une semaine plus tard, la production plante — sur un tableau de 1000 éléments avec des doublons et des nombres négatifs. Cela vous dit quelque chose ?

\n\n

Les tests TypeScript traditionnels basés sur des exemples (example-based testing) ne vérifient que les scénarios que vous avez imaginés. Et si un bug se cachait dans une combinaison à laquelle vous n'avez pas pensé ? C'est là qu'intervient le test basé sur les propriétés (property-based testing) — une approche qui génère automatiquement des milliers de données d'entrée aléatoires et vérifie les invariants de votre code.

\n\n

Dans cet article, nous verrons comment appliquer le property-based testing en TypeScript avec la bibliothèque fast-check, quelles propriétés vérifier et comment cela change l'approche du développement.

\n\n

Qu'est-ce que le test basé sur les propriétés ?

\n\n

Au lieu d'écrire :

\n
it('trie [3, 1, 2] → [1, 2, 3]', () => {\n  expect(sort([3, 1, 2])).toEqual([1, 2, 3]);\n});\n
\n\n

Vous décrivez une propriété (property) — une assertion qui doit être vraie pour toutes les données d'entrée valides :

\n
it('un tableau trié ne modifie pas l'ordre des éléments', () => {\n  fc.assert(\n    fc.property(fc.array(fc.integer()), (arr) => {\n      const sorted = sort(arr);\n      // Propriété 1 : la longueur est conservée\n      expect(sorted.length).toBe(arr.length);\n      // Propriété 2 : chaque élément du tableau d'origine est présent\n      // Propriété 3 : le tableau est trié par ordre croissant\n      for (let i = 1; i < sorted.length; i++) {\n        expect(sorted[i - 1]).toBeLessThanOrEqual(sorted[i]);\n      }\n    })\n  );\n});\n
\n\n

La bibliothèque fast-check générera elle-même des centaines de tableaux aléatoires (y compris vides, avec NaN, avec des nombres décimaux, avec des valeurs énormes) et exécutera votre test sur chacun d'eux. Si l'un d'eux échoue — vous serez informé du problème.

\n\n

Pourquoi est-ce particulièrement utile en TypeScript ?

\n\n

TypeScript offre un typage statique, mais il ne garantit pas la correction de la logique. Le test basé sur les propriétés en TypeScript comble le fossé entre « les types correspondent » et « le code fonctionne correctement ». C'est particulièrement utile pour :

\n\n
    \n
  • Les fonctions de transformation de données (map, filter, reduce)
  • \n
  • Les analyseurs et validateurs
  • \n
  • Les algorithmes à logique non triviale
  • \n
  • Les fonctions manipulant des valeurs limites
  • \n
\n\n

La bibliothèque fast-check : démarrage rapide

\n\n

Installez fast-check dans votre projet :

\n
npm install --save-dev fast-check\n
\n\n

Syntaxe de base :

\n
import fc from 'fast-check';\n\ndescribe('propriétés de la fonction reverse', () => {\n  it('reverse(reverse(arr)) === arr', () => {\n    fc.assert(\n      fc.property(fc.array(fc.anything()), (arr) => {\n        expect(reverse(reverse(arr))).toEqual(arr);\n      })\n    );\n  });\n});\n
\n\n

Ici, fc.array(fc.anything()) est un générateur de tableaux arbitraires. Vous pouvez combiner les générateurs :

\n
    \n
  • fc.integer() — nombres entiers
  • \n
  • fc.string() — chaînes de caractères
  • \n
  • fc.record({ name: fc.string(), age: fc.integer({ min: 0, max: 120 }) }) — objets
  • \n
  • fc.oneof(...) — sélection aléatoire parmi plusieurs générateurs
  • \n
\n\n

Quelles propriétés vérifier ? 5 exemples pratiques

\n\n

1. Invariance (réversibilité)

\n

Toute opération qui a une opération inverse doit être réversible :

\n
it('sérialisation et désérialisation JSON', () => {\n  fc.assert(\n    fc.property(fc.object(), (obj) => {\n      const json = JSON.stringify(obj);\n      const parsed = JSON.parse(json);\n      expect(parsed).toEqual(obj);\n    })\n  );\n});\n<                

Blogs

Book Recommendations