# Benchmark continu : détecter en CI une fonction PHP qui a régressé

> Mesurer le temps d'exécution d'une fonction précise, comparé à une ligne de base, pour attraper une régression de performance avant la mise en ligne.

- Auteur : Clément Hadrot
- Publié le : 2024-10-07
- Mis à jour le : 2024-10-07
- Catégorie : Tests
- URL : https://wpmoderne.dev.wordpress-developpement.fr/tests/benchmark-continu-fonction-php-regression-ci/

## L’essentiel

- Une régression de fonction passe inaperçue dans les tests fonctionnels classiques
- Une ligne de base versionnée sert de référence objective
- Le seuil de tolérance évite les faux positifs liés au bruit machine

Une fonction de calcul de prix dégressif, appelée à chaque affichage de fiche produit sur un site e-commerce, est passée de 3 millisecondes à 210 millisecondes après l'ajout d'une nouvelle règle de remise imbriquée dans une boucle mal placée. Aucun test fonctionnel n'a détecté le problème : la fonction retournait toujours le bon prix, juste beaucoup plus lentement. C'est un client final qui a signalé la lenteur du site, trois semaines après la mise en ligne du correctif fautif.

Un test fonctionnel vérifie un résultat, jamais un temps. Pour attraper ce genre de régression avant qu'elle n'atteigne la production, il faut un test différent, dont la seule assertion porte sur la durée d'exécution comparée à une référence connue.

## Architecture du dispositif de benchmark continu

```
projet/
├── src/
│   └── Pricing/
│       └── DegressiveCalculator.php
├── benchmarks/
│   ├── DegressiveCalculatorBench.php
│   └── baseline.json          (ligne de base versionnée dans le dépôt)
└── .github/
    └── workflows/
        └── benchmark.yml
```

## Écrire le benchmark avec PHPBench

> L'essentiel à retenir : Une régression de fonction passe inaperçue dans les tests fonctionnels classiques ; Une ligne de base versionnée sert de référence objective ; Le seuil de tolérance évite les faux positifs liés au bruit machine

[PHPBench](https://github.com/phpbench/phpbench) est l'outil de référence pour ce type de mesure en PHP, avec un fonctionnement proche de PHPUnit mais orienté vers la mesure plutôt que l'assertion de résultat :

```
/**
 * @BeforeMethods({"preparerContexte"})
 * @Revs(1000)
 * @Iterations(5)
 */
class DegressiveCalculatorBench {

    private $produits;

    public function preparerContexte() {
        $this->produits = array_fill( 0, 50, [ 'prix' => 29.90, 'quantite' => 12 ] );
    }

    public function bench_calcul_prix_degressif() {
        $calculateur = new DegressiveCalculator();
        foreach ( $this->produits as $produit ) {
            $calculateur->calculer( $produit['prix'], $produit['quantite'] );
        }
    }
}
```

L'annotation `@Revs(1000)` répète l'appel mille fois par itération pour lisser le bruit de mesure, et `@Iterations(5)` répète l'ensemble cinq fois pour calculer un écart-type fiable.

## Comparer contre une ligne de base versionnée

PHPBench sait comparer une exécution à un fichier de référence généré préalablement et commité dans le dépôt :

```
# générer la ligne de base une première fois, après validation manuelle des temps
vendor/bin/phpbench run --store --tag=baseline

# en CI, comparer contre cette référence
vendor/bin/phpbench run --ref=baseline --report=aggregate
```

PHPBench renvoie un code de sortie différent de zéro si un seuil de régression configuré est dépassé, ce qui suffit à faire échouer le job GitHub Actions correspondant.

## Définir un seuil de tolérance réaliste

Un seuil à 0 % de tolérance ferait échouer le pipeline à la moindre variation de charge du runner CI partagé, un bruit qui n'a rien à voir avec une vraie régression du code. Après plusieurs semaines d'observation sur nos runners GitHub Actions standards, nous avons fixé la tolérance à 40 % d'écart avant échec, un seuil volontairement large qui absorbe le bruit machine tout en attrapant sans difficulté une régression d'un facteur 10 comme celle de l'incident initial.

```
# .phpbench.json
{
    "runner.assert": "mode(current.time) < mode(baseline.time) * 1.4"
}
```

## Intégrer au pipeline GitHub Actions

```
- name: Benchmark de performance
  run: vendor/bin/phpbench run --ref=baseline --report=aggregate --fail-on-regression
```

- Ne benchmarker que les fonctions identifiées comme critiques par leur fréquence d'appel, pas l'intégralité du code : le coût de maintenance croît vite avec le nombre de benchmarks.
- Regénérer la ligne de base explicitement et volontairement après une optimisation reconnue, jamais automatiquement.
- Exécuter les benchmarks sur un runner dédié si possible, pour limiter le bruit de mesure lié au partage de ressources.

## Pour aller plus loin

Ce dispositif ne remplace pas Lighthouse CI, qui mesure la performance perçue d'une page entière côté navigateur : il se concentre sur la performance d'une fonction PHP isolée, à un grain beaucoup plus fin. Les deux approches sont complémentaires plutôt que redondantes, la première protégeant l'expérience utilisateur globale, la seconde protégeant les fonctions critiques identifiées comme sensibles par l'équipe.
