# Organiser le dossier tests d’un projet WordPress : unit, integration, e2e

> Arborescence type, bootstraps séparés par niveau de test, configuration PHPUnit multiple et commandes npm et composer associées, pour un projet qui ne s'y perd plus.

- Auteur : Clément Hadrot
- Publié le : 2026-01-29
- Mis à jour le : 2026-01-29
- Catégorie : Tests
- URL : https://wpmoderne.dev.wordpress-developpement.fr/tests/organiser-dossier-tests-projet-wordpress/

## L’essentiel

- Un bootstrap unique pour trois niveaux de tests finit toujours par ralentir tout le monde
- Chaque niveau de test a sa propre configuration PHPUnit, pas une seule fusionnée
- Les scripts Composer et npm documentent la commande à taper, pas seulement le résultat attendu

Sur un projet repris auprès d'une autre agence, le dossier `tests` contenait un mélange non trié de tests unitaires purs, de tests nécessitant une base de données WordPress complète, et de scénarios Playwright, le tout piloté par un unique fichier `phpunit.xml` et un unique script npm nommé sobrement `test`. Résultat concret : lancer « les tests » pendant le développement d'une fonction pure prenait près de deux minutes, temps de démarrage d'une base MySQL complète compris, pour vérifier une simple fonction de formatage de prix qui n'avait besoin d'aucune base de données.

Réorganiser ce dossier a demandé moins d'une journée, pour un gain de confort quotidien qui s'est fait sentir immédiatement : les tests unitaires purs se lancent désormais en une poignée de secondes, indépendamment du reste, ce qui change concrètement la fréquence à laquelle l'équipe les exécute pendant qu'elle code.

## L'arborescence retenue

```
tests/
├── unit/
│   ├── bootstrap.php
│   └── Calcul/
│       └── CalculTarifTest.php
├── integration/
│   ├── bootstrap.php
│   └── Rest/
│       └── RouteDevisTest.php
├── e2e/
│   ├── playwright.config.ts
│   └── specs/
│       └── tunnel-commande.spec.ts
├── phpunit-unit.xml.dist
└── phpunit-integration.xml.dist
```

Le principe directeur est simple : un dossier par niveau de test, chacun avec son propre bootstrap et sa propre configuration, plutôt qu'un seul fichier de configuration qui tente de tout couvrir avec des groupes ou des filtres complexes à retenir.

## Deux bootstraps, deux besoins réels

Le bootstrap des tests unitaires purs ne charge que l'autoload Composer du projet, sans jamais toucher à WordPress lui-même :

```
<?php
// tests/unit/bootstrap.php
require_once dirname( __DIR__, 2 ) . '/vendor/autoload.php';
```

Le bootstrap des tests d'intégration, lui, charge la suite de tests officielle de WordPress avec sa base de données dédiée, exactement comme le ferait un projet classique basé sur `WP_UnitTestCase` :

```
<?php
// tests/integration/bootstrap.php
$_tests_dir = getenv( 'WP_TESTS_DIR' ) ?: '/tmp/wordpress-tests-lib';
require_once $_tests_dir . '/includes/functions.php';

tests_add_filter( 'muplugins_loaded', function () {
    require dirname( __DIR__, 2 ) . '/mon-extension.php';
} );

require $_tests_dir . '/includes/bootstrap.php';
```

> L'essentiel à retenir : Un bootstrap unique pour trois niveaux de tests finit toujours par ralentir tout le monde ; Chaque niveau de test a sa propre configuration PHPUnit, pas une seule fusionnée ; Les scripts Composer et npm documentent la commande à taper, pas seulement le résultat attendu

## Deux fichiers de configuration PHPUnit distincts

Chaque niveau reçoit son propre fichier de configuration, avec son propre bootstrap et son propre dossier de tests ciblé, ce qui rend la commande à taper explicite et évite tout filtre de groupe à retenir :

```
<!-- tests/phpunit-unit.xml.dist -->
<phpunit bootstrap="unit/bootstrap.php">
    <testsuites>
        <testsuite name="unit">
            <directory>unit</directory>
        </testsuite>
    </testsuites>
</phpunit>

<!-- tests/phpunit-integration.xml.dist -->
<phpunit bootstrap="integration/bootstrap.php">
    <testsuites>
        <testsuite name="integration">
            <directory>integration</directory>
        </testsuite>
    </testsuites>
</phpunit>
```

## Les commandes exposées, documentées par leur nom même

Plutôt qu'un unique script `test` ambigu, on expose des scripts Composer nommés explicitement selon leur portée, chacun invoquant le bon fichier de configuration :

```
{
    "scripts": {
        "test:unit": "phpunit -c tests/phpunit-unit.xml.dist",
        "test:integration": "phpunit -c tests/phpunit-integration.xml.dist",
        "test:e2e": "playwright test --config=tests/e2e/playwright.config.ts",
        "test:all": [
            "@test:unit",
            "@test:integration",
            "@test:e2e"
        ]
    }
}
```

Un développeur qui modifie une fonction de calcul pure sait qu'il lui suffit de `composer test:unit` pendant son travail courant, réservant `composer test:all` aux vérifications avant de pousser sa pull request, sans avoir à connaître par cœur quel dossier correspond à quel besoin.

### Côté JavaScript et Gutenberg

La même logique s'applique aux tests Jest sur les blocs, avec un fichier `jest.config.js` qui cible spécifiquement les fichiers de test unitaires JS, séparé de la configuration Playwright utilisée pour l'E2E, chacun invocable indépendamment via un script npm dédié.

## Éviter le piège du dossier fourre-tout « helpers »

Une tentation fréquente consiste à créer un unique dossier `tests/helpers` partagé entre tous les niveaux de tests, qui finit par accumuler des fonctions utilitaires couplées implicitement à un seul niveau alors qu'elles sont censées être génériques. On préfère un sous-dossier d'aides spécifique à chaque niveau (`tests/unit/Helpers`, `tests/integration/Helpers`), et on ne remonte une fonction vers un dossier vraiment partagé qu'après avoir constaté un besoin réel dans au moins deux niveaux distincts.

> Une arborescence de tests que l'équipe doit expliquer verbalement à chaque nouveau contributeur n'est pas assez claire : le nom des dossiers et des scripts doit porter la réponse à lui seul.

## Intégration à la CI, en jobs séparés

Cette séparation se reflète naturellement dans le pipeline de CI, avec un job dédié par niveau de test, ce qui permet de paralléliser leur exécution et d'identifier immédiatement, dans les résultats affichés sur la pull request, si l'échec provient d'une simple fonction de calcul ou d'un scénario d'intégration plus lourd à diagnostiquer.

## En résumé

Séparer clairement l'arborescence, les bootstraps et les configurations selon les trois niveaux classiques de tests, unitaire, intégration et E2E, n'est pas un exercice de pure esthétique d'organisation : c'est ce qui détermine directement la fréquence à laquelle l'équipe lance réellement les tests pendant son travail quotidien. Un projet où lancer un test unitaire simple prend deux minutes finit toujours par voir cette pratique abandonnée en pratique, quelle que soit la bonne volonté affichée au démarrage du projet.
