# Paralléliser une suite PHPUnit avec Paratest sans collision de base de données

> Une suite PHPUnit qui dépasse dix minutes ralentit toute l'équipe. Paratest répartit les tests sur plusieurs processus, à condition d'isoler correctement chaque base.

- Auteur : WordPress Développement
- Publié le : 2021-01-21
- Mis à jour le : 2021-01-21
- Catégorie : Tests
- URL : https://www.wpmoderne.fr/tests/paratest-parallelisation-phpunit-sans-collision/

## L’essentiel

- Chaque processus parallèle a besoin de sa propre base de données isolée
- La variable TEST_TOKEN de Paratest permet de suffixer dynamiquement le nom de la base
- Les fichiers partagés en écriture restent le piège numéro un

`vendor/bin/paratest --processes=4` : cette seule commande peut diviser par quatre la durée d'exécution d'une suite PHPUnit, à condition qu'elle ne collisionne pas immédiatement sur la même base de données. C'est précisément ce piège qui rend la parallélisation décevante la première fois qu'une équipe l'essaie.

Paratest s'installe par-dessus PHPUnit et distribue les fichiers de test entre plusieurs processus qui s'exécutent en même temps, chacun sur son propre cœur du processeur. Ce billet explique comment isoler les bases de test entre ces processus et évite volontairement le sujet distinct de la parallélisation des tests de bout en bout (E2E), qui pose d'autres contraintes.

## Le problème par défaut : une seule base pour tous les processus

Sans configuration particulière, chaque processus lancé par Paratest se connecte à la même base de données définie dans `wp-tests-config.php`. Deux processus qui créent un article en parallèle peuvent alors se marcher dessus, provoquant des échecs aléatoires impossibles à reproduire de façon stable en exécution séquentielle.

```
vendor/bin/paratest --processes=4 --configuration=phpunit.xml.dist
# PHPUnit\Framework\ExpectationFailedException:
# Failed asserting that 3 matches expected 1.
# (un autre processus a inséré des articles pendant le test)
```

## Isoler une base par processus grâce au jeton de Paratest

Paratest expose une variable d'environnement, `TEST_TOKEN`, qui prend une valeur différente pour chaque processus lancé (0, 1, 2, 3 sur quatre processus). L'astuce consiste à l'utiliser pour suffixer dynamiquement le nom de la base de données dans la configuration des tests :

```
<?php
// wp-tests-config.php
$jeton = getenv( 'TEST_TOKEN' ) ?: '0';

define( 'DB_NAME', 'wordpress_test_' . $jeton );
define( 'DB_USER', 'root' );
define( 'DB_PASSWORD', '' );
define( 'DB_HOST', 'localhost' );
```

Chaque processus dispose ainsi de sa propre base (`wordpress_test_0`, `wordpress_test_1`, etc.), créée au préalable via un script qui boucle sur le nombre de processus prévu :

```
for i in 0 1 2 3; do
  mysql -u root -e "CREATE DATABASE IF NOT EXISTS wordpress_test_$i"
done
```

> L'essentiel à retenir : Chaque processus parallèle a besoin de sa propre base de données isolée ; La variable TEST_TOKEN de Paratest permet de suffixer dynamiquement le nom de la base ; Les fichiers partagés en écriture restent le piège numéro un

## Les fichiers partagés, deuxième source de collision

Une fois la base isolée, un autre piège attend souvent les équipes : les fichiers écrits sur disque pendant les tests, notamment dans le dossier `wp-content/uploads` d'une installation de test partagée. Deux processus qui écrivent simultanément dans le même chemin peuvent produire des erreurs de permission ou des fichiers corrompus.

- Prévoir un dossier d'upload distinct par jeton de processus, sur le même modèle que la base de données.
- Vérifier qu'aucun test n'écrit dans un chemin absolu codé en dur, ce qui empêcherait toute isolation.
- Se méfier des caches de fichiers (transients basés sur des fichiers, caches d'objets sur disque) qui peuvent survivre entre processus si le chemin n'est pas lui aussi isolé.

## Répartir intelligemment les fichiers de test

Par défaut, Paratest répartit les fichiers de test en fonction du nombre de processus disponibles, sans tenir compte de leur durée respective. Un fichier contenant un test long peut donc se retrouver seul sur un processus pendant que les trois autres ont déjà terminé. L'option `--runner=WrapperRunner` combinée à un ordre de fichiers optimisé par expérience réduit ce déséquilibre :

```
vendor/bin/paratest --processes=4 --runner=WrapperRunner --testsuite=integration
```

## Vérifier le gain réel avant de généraliser

La parallélisation n'est pas gratuite : chaque processus démarre son propre bootstrap WordPress, ce qui consomme de la mémoire et du temps de démarrage. Sur une suite courte, le gain peut être nul, voire négatif, une fois ce coût de démarrage pris en compte. Il vaut mieux comparer un temps d'exécution avant et après sur la suite réelle du projet plutôt que de généraliser un chiffre lu ailleurs.

1. Mesurer le temps d'exécution séquentiel de référence.
2. Activer Paratest avec un nombre de processus égal au nombre de cœurs disponibles en intégration continue.
3. Comparer le temps obtenu, en gardant à l'esprit que la mémoire disponible peut limiter le nombre de processus utile.

> Isoler la base avant de mesurer un gain de vitesse : sinon, le premier échec aléatoire sera imputé à tort à Paratest lui-même.

## En résumé

Paratest peut réduire fortement la durée d'une suite PHPUnit, mais seulement si chaque processus dispose de sa propre base de données, identifiée par le jeton `TEST_TOKEN`, et si les fichiers écrits sur disque suivent la même logique d'isolation. Sans cette préparation, la parallélisation produit surtout des échecs aléatoires difficiles à diagnostiquer.
