# Tester le hasard : un cas d’école du Property-Based Testing

> Le PBT teste qu'un invariant métier est toujours vérifié quels que soient les paramètres et la valeur de retour de la fonction objet du test.

- Author: Mathieu Eveillard
- Published: 2025-12-10
- Tags: Artisanat logiciel
- Canonical: https://www.mathieueveillard.com/fr/articles/property-based-testing/
- Translation (en): https://www.mathieueveillard.com/articles/property-based-testing.md

![Tester le hasard : un cas d’école du Property-Based Testing](https://www.mathieueveillard.com/articles/2025-12-10-property-based-testing/property-based-testing.webp)

Le test déterministe, basé sur des exemples tels que `f(0) = 1`, n'est qu'une modalité de test [parmi d'autres](https://www.mathieueveillard.com/fr/articles/50-shades-of-tests). Dans certains cas, tel celui de fonctions reposant sur la génération de nombres au hasard, on peut faire mieux.

## Formulation du problème

Imaginons une fonction `random` renvoyant un nombre réel compris entre deux bornes `min` et `max`, dont l'implémentation s'appuierait naturellement sur la fonction `Math.random`, qui renvoie un nombre réel compris entre 0 et 1 :

```typescript
type Parameters = {
  min: number;
  max: number;
};

const random = ({ min, max }: Parameters): number =>
  min + (max - min) * Math.random();
```

Comment tester cette fonction ?

Cette question demande en fait de savoir ce que l'on veut tester :

- Le fait que les valeurs générées sont bien comprises entre `min` et `max` ?
- Ou bien la distribution de probabilités entre `min` et `max` ?

Dans notre cas, nous pouvons faire l'hypothèse que la distribution des valeurs de `Math.random` est effectivement uniforme, conformément à sa [spécification](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random). C'est donc la valeur ajoutée de notre fonction, à savoir le fait « d'étirer » le hasard entre `min` et `max`, que nous souhaitons éprouver.

Comment procéder ? **Le (pseudo)-hasard ayant le bon goût d'être imprévisible, nous ne saurions procéder à un test déterministe, basé sur un exemple**. En effet, tester que `random({ min: 1, max: 10 }) === 5` serait non seulement faux la plupart du temps, mais surtout cela n'aurait aucun sens.

## Approche 1 : l'inversion de dépendance pour « contrôler le hasard »

Une première solution est d'injecter la fonction génératrice de hasard afin de pouvoir contrôler sa valeur de retour dans un test. Pour cela, nous procédons par **inversion de dépendance** (IoD) : initialement, la fonction `random` dépendait de `Math.random` ; à présent, elle définit elle-même et avec ses propres mots ce dont elle a besoin pour travailler, c'est-à-dire `RandomGenerator`, et charge au « monde extérieur », c'est-à-dire à l'appelant, d'injecter une fonction qui satisfait ce contrat (**injection de dépendance**).

Ce qui nous donne le code suivant :

```ts
type RandomGenerator = () => number;

type Parameters = {
  min: number;
  max: number;
};

export const random =
  (generator: RandomGenerator) =>
  ({ min, max }: Parameters): number =>
    min + (max - min) * generator();

test("The lowest value possible should be 'min'", () => {
  const generator = () => 0;
  expect(random(generator)({ min: 1, max: 10 })).toEqual(1);
});

test("The highest value possible should be 'max'", () => {
  const generator = () => 1;
  expect(random(generator)({ min: 1, max: 10 })).toEqual(10);
});
```

Cette solution fonctionne, mais vous conviendrez avec moi qu'elle est tout de même très _aware_ de l'implémentation. Dit de façon moins « [Jean-Claude](https://youtu.be/8ECgLA2MjFo?si=Uxv1q0jHSTBUVQTb) », cela signifie que **si l'implémentation change, le test doit changer lui aussi**. Nous souhaitons tester un certain comportement, peu importe l'implémentation, point.

Heureusement, on peut faire mieux.

## Approche 2 : le Property-based testing pour tester un invariant métier

Par contraste avec l'Example-based testing (le test déterministe tel que nous le connaissons), **le Property-based testing se propose de vérifier qu'un invariant métier est toujours vérifié pour des valeurs d'entrée prises au hasard et en grand nombre** (généralement > 100, mais vous pouvez paramétrer le nombre de tirages).

Voyons ce que cela donne avec la bibliothèque [`fast-check`](https://fast-check.dev/) :

```ts
import fc from "fast-check";

test("The generated value should fall within the (min, max) interval", () => {
  fc.assert(
    fc.property(
      fc.record({ x1: fc.integer(), x2: fc.integer({ min: 1 }) }),
      ({ x1, x2 }): boolean => {
        const min = x1;
        const max = min + x2;
        const actual = random({ min, max });
        return min <= actual && actual <= max;
      },
    ),
  );
});
```

Ici, `fast-check` tire pour nous deux nombres entiers relatifs au hasard, `x1` et `x2`, dont nous nous servons pour construire l'intervalle `[min, max]`. Ensuite, nous invoquons la fonction `random` objet de notre test et vérifions simplement que la valeur qu'elle retourne est bien comprise entre les bornes de l'intervalle. Remarquez qu'à aucun moment nous n'avons connaissance des valeurs de `min`, `max`, ni de `random({ min, max })`. C'est toute la beauté de la chose : nous testons un invariant.

## Ce que garantit vraiment le Property-based testing

Cet exemple est l'un des plus simples que je puisse envisager pour expliquer le Property-based testing, mais il est typique, car le hasard est un cas d'usage parfait de ce type de test.

Quelle sécurité ce type d'assertion nous procure-t-il ?

**Ce test ne prouve rien de plus qu'un test classique** : ce n'est pas une démonstration du bon fonctionnement de votre code, simplement un test. Même si vous tirez 1000 valeurs à chaque test, que vous effectuez le test 100 fois et que la propriété est vérifiée les 100000 fois, vous ne pouvez pas exclure l'éventualité de tomber sur un contre-exemple la 100001 ème fois. Mais statistiquement, c'est tout de même assez peu probable, et pour du développement, nous pouvons nous en satisfaire.

Gardez simplement à l'esprit que vous augmentez votre degré de sécurité en augmentant le nombre de tirages.
