vendredi 25 septembre 2026

À propos

Contact

Tests

Pourquoi vos tests échouent en CI alors qu’ils passent en local (et comment le corriger)

Un test vert en local et rouge en CI use la confiance de toute l'équipe. Voici les quatre causes les plus fréquentes de tests instables sur des projets WordPress, et leurs correctifs.

Par Clément Hadrot • 6 octobre 2021 • 6 min de lecture • Aucun commentaire
Pourquoi vos tests échouent en CI alors qu'ils passent en local (et comment le corriger)

« Ça marche chez moi » est probablement la phrase la plus entendue par toute équipe qui maintient une intégration continue. Un test qui passe systématiquement en local et échoue de façon intermittente en CI ne signale pas forcément un bug dans le code testé : il révèle presque toujours une différence d’environnement ou une hypothèse implicite dans le test lui-même. Sur nos projets WordPress, quatre causes reviennent régulièrement, et elles se corrigent presque toutes de la même façon : en rendant explicite ce que le test supposait implicitement.

Cet article détaille ces quatre causes, avec des exemples concrets tirés de suites PHPUnit et Jest réelles, pour vous éviter des heures de débogage sur un pipeline GitHub Actions qui semble se comporter de façon aléatoire.

Cause n°1 : le fuseau horaire et la locale

La machine d’un développeur tourne généralement dans son fuseau horaire local, avec une locale française. Un runner GitHub Actions, lui, tourne par défaut en UTC avec une locale anglaise. Un test qui vérifie une date formatée avec date_i18n(), ou qui compare une heure sans passer par current_time( 'timestamp' ), peut très bien passer en local et échouer en CI simplement parce que l’heure calculée diffère de plusieurs heures.

// Fragile : dépend du fuseau horaire du serveur qui exécute le test
public function test_larticle_est_publie_aujourdhui() {
    $post_id = $this->factory()->post->create( array(
        'post_date' => date( 'Y-m-d H:i:s' ),
    ) );

    $this->assertEquals( date( 'Y-m-d' ), get_the_date( 'Y-m-d', $post_id ) );
}

La correction consiste à fixer explicitement le fuseau horaire de test, ou à utiliser systématiquement les fonctions WordPress conscientes du fuseau horaire réglé dans les options du site, plutôt que les fonctions PHP natives :

public function test_larticle_est_publie_aujourdhui() {
    $maintenant = current_time( 'mysql' );
    $post_id = $this->factory()->post->create( array(
        'post_date' => $maintenant,
    ) );

    $this->assertEquals(
        current_time( 'Y-m-d' ),
        get_the_date( 'Y-m-d', $post_id )
    );
}

Cause n°2 : des tests qui dépendent de leur ordre d’exécution

PHPUnit n’exécute pas nécessairement les tests dans l’ordre où ils apparaissent dans le fichier, et cet ordre peut varier entre deux environnements selon la configuration (--order-by, parallélisation). Un test qui suppose implicitement qu’un autre test a déjà créé une donnée, par exemple une option ou un utilisateur, se comporte de façon imprévisible dès que l’ordre change.

Le symptôme classique : une suite qui passe intégralement en local, où l’ordre reste stable d’une exécution à l’autre, mais échoue sporadiquement en CI dès qu’un test est exécuté seul, en isolation, ou dans un ordre différent lié à la parallélisation du runner.

Diagnostiquer ce type de dépendance

La commande suivante force un ordre aléatoire et aide à révéler ces dépendances cachées avant qu’elles ne surprennent en CI :

vendor/bin/phpunit --order-by=random

Si un test échoue uniquement avec cette option, il dépend presque certainement d’un état laissé par un autre test. La solution consiste à rendre chaque test autonome, en créant explicitement ses propres données dans sa méthode ou dans setUp(), sans jamais compter sur un effet de bord d’un test voisin.

L'essentiel à retenir : Le fuseau horaire et la locale, coupables fréquents et invisibles ; Des tests qui dépendent silencieusement de leur ordre d'exécution ; Un environnement CI plus lent qui révèle des attentes trop courtes

Cause n°3 : des attentes trop courtes sur un environnement plus lent

Un runner CI partagé est souvent plus lent qu’une machine de développement dédiée, surtout sous forte charge. Dans les tests E2E avec Puppeteer ou Jest, un délai fixe du type page.waitForTimeout( 500 ) qui suffit largement en local peut devenir insuffisant en CI, provoquant des échecs intermittents que rien ne permet de reproduire facilement.

// Fragile : suppose que 500 ms suffisent toujours
await page.waitForTimeout( 500 );
const texte = await page.$eval( '.mon-bloc', el => el.textContent );

// Plus robuste : attend explicitement l'état recherché
await page.waitForSelector( '.mon-bloc.est-charge' );
const texte = await page.$eval( '.mon-bloc', el => el.textContent );

La règle générale : remplacer systématiquement un délai fixe par une attente conditionnelle sur un état observable (sélecteur présent, réponse réseau reçue, texte affiché), qui s’adapte naturellement à la vitesse réelle de l’environnement.

Cause n°4 : des différences de configuration PHP ou MySQL

La version exacte de PHP, de MySQL, ou même certains réglages comme strict mode côté base de données, peuvent différer entre le poste d’un développeur et le runner CI. Un test qui insère une valeur légèrement hors des bornes attendues d’une colonne peut être silencieusement tronqué en local (MySQL en mode permissif) et provoquer une erreur explicite en CI (MySQL en mode strict), ou inversement.

  • Documentez et versionnez la configuration exacte utilisée en CI (version PHP, version MySQL) dans un fichier lisible par toute l’équipe.
  • Utilisez, quand c’est possible, la même image Docker en local et en CI pour éliminer ce type de divergence à la source.
  • Ajoutez la version PHP et MySQL utilisée dans le rapport de sortie de la CI, pour accélérer le diagnostic la prochaine fois qu’un écart apparaît.

Sur nos projets, dès qu’un test échoue en CI sans échouer en local, la première question que nous posons n’est jamais « qu’est-ce que ce test vérifie », mais « de quoi ce test dépend qu’il ne déclare pas explicitement ». Neuf fois sur dix, la réponse est le fuseau horaire, l’ordre d’exécution, ou un délai trop court.

En résumé

Un test instable entre local et CI n’est presque jamais un problème de chance : c’est un test qui s’appuie sur une hypothèse implicite que l’environnement CI ne partage pas. Fuseau horaire, ordre d’exécution, délais fixes et divergence de configuration expliquent l’immense majorité des cas rencontrés sur nos projets WordPress. Rendre ces dépendances explicites, plutôt que de relancer le pipeline en espérant que ça passe, est le seul moyen de retrouver une suite de tests en laquelle toute l’équipe peut avoir confiance.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi