# PHPUnit : un trait d’assertions maison, sans hériter d’une classe abstraite

> Répéter la même vérification dans dix classes de test alourdit le code. Un trait PHP la centralise sans imposer de hiérarchie d'héritage rigide.

- Auteur : WordPress Développement
- Publié le : 2020-05-20
- Mis à jour le : 2020-05-20
- Catégorie : Tests
- URL : https://www.wpmoderne.fr/tests/trait-assertions-maison-phpunit-sans-heritage/

## L’essentiel

- Une assertion réutilisable sans classe parente imposée
- Compatible avec un seul héritage déjà utilisé
- S'applique classe par classe, à la demande

Une agence qui gère plusieurs extensions WordPress finit toujours par écrire la même vérification à plusieurs endroits : contrôler qu'un article possède bien un certain méta, que sa taxonomie est correctement renseignée, ou qu'une réponse REST respecte un format précis. Copiée d'une classe de test à l'autre, cette vérification devient vite une source d'incohérences : une version légèrement modifiée dans un fichier, oubliée dans un autre.

La tentation naturelle consiste à créer une classe abstraite commune dont toutes les classes de test hériteraient. Mais PHP n'autorise qu'un seul héritage, et cette classe abstraite entre alors en concurrence avec `WP_UnitTestCase` ou `TestCase`, dont chaque classe de test a déjà besoin. Un trait PHP contourne cette limite proprement.

## Le problème de la hiérarchie unique

Dès qu'une classe de test hérite de `WP_UnitTestCase`, elle ne peut plus hériter d'une autre classe. Introduire une classe abstraite intermédiaire, par exemple `Mon_Assertions_Abstraites`, imposerait de faire hériter cette classe elle-même de `WP_UnitTestCase`, ce qui fonctionne, mais rigidifie l'architecture : toute nouvelle famille de tests ayant besoin d'un autre socle se retrouve bloquée.

```
// Fonctionne, mais fige toute la hiérarchie de test
abstract class Mon_Assertions_Abstraites extends WP_UnitTestCase {
    protected function assertArticlePublie( int $post_id ): void {
        $statut = get_post_status( $post_id );
        $this->assertSame( 'publish', $statut );
    }
}
```

Un trait règle ce problème en apportant des méthodes à une classe sans imposer de lien de parenté. Il peut être mélangé (« *use* ») dans n'importe quelle classe de test, quelle que soit sa classe parente réelle.

## Écrire le trait

> L'essentiel à retenir : Une assertion réutilisable sans classe parente imposée ; Compatible avec un seul héritage déjà utilisé ; S'applique classe par classe, à la demande

```
trait AssertionsArticleTrait {

    protected function assertArticlePublie( int $post_id ): void {
        $statut = get_post_status( $post_id );
        $this->assertSame(
            'publish',
            $statut,
            "L'article {$post_id} devrait être publié, statut actuel : {$statut}"
        );
    }

    protected function assertArticlePossedeMeta( int $post_id, string $cle, $valeur_attendue ): void {
        $valeur = get_post_meta( $post_id, $cle, true );
        $this->assertSame( $valeur_attendue, $valeur );
    }
}
```

Ce trait ne contient aucune logique de préparation ni de nettoyage, uniquement des méthodes d'assertion. Il suppose que la classe qui l'utilise possède déjà les méthodes `assertSame` fournies par PHPUnit, ce qui est le cas dès que cette classe hérite, directement ou indirectement, de `PHPUnit\Framework\TestCase`.

## L'utiliser dans une classe de test

```
class ArticlePublicationTest extends WP_UnitTestCase {

    use AssertionsArticleTrait;

    public function test_publication_directe(): void {
        $post_id = self::factory()->post->create( [ 'post_status' => 'publish' ] );
        $this->assertArticlePublie( $post_id );
    }

    public function test_meta_langue_correcte(): void {
        $post_id = self::factory()->post->create();
        update_post_meta( $post_id, '_langue', 'fr' );
        $this->assertArticlePossedeMeta( $post_id, '_langue', 'fr' );
    }
}
```

La classe garde son héritage naturel de `WP_UnitTestCase`, tout en gagnant deux méthodes d'assertion supplémentaires par simple import du trait. Une autre classe de test, dans un contexte n'ayant rien à voir avec les articles, peut utiliser ce même trait sans aucune contrainte de hiérarchie.

## Ce que le trait ne remplace pas

Un trait ne peut pas définir de constructeur au sens strict, ni gérer de logique de préparation partagée entre plusieurs traits utilisés simultanément de façon fiable : deux traits définissant une méthode `setUp` entrent en conflit et doivent être résolus explicitement avec la syntaxe `insteadof`. Pour de la préparation de données complexe, une factory ou une méthode utilitaire appelée explicitement depuis `setUp` reste plus lisible qu'un trait surchargé de responsabilités.

> Un trait d'assertions doit rester ennuyeux : des vérifications courtes, sans effet de bord, qui décrivent ce qu'on attend plutôt que comment le préparer.

## Organiser plusieurs traits par domaine

Sur un projet volumineux, un seul trait fourre-tout devient vite difficile à maintenir. Le découper par domaine fonctionnel, un trait pour les articles, un autre pour les commandes WooCommerce, un troisième pour les réponses REST, garde chaque fichier court et permet à une classe de test de n'importer que ce dont elle a réellement besoin :

- `AssertionsArticleTrait` pour tout ce qui touche aux contenus publiés.
- `AssertionsReponseRestTrait` pour la structure des réponses d'API.
- `AssertionsUtilisateurTrait` pour les rôles et capacités.

## En résumé

Un trait PHP offre exactement ce qu'une classe abstraite commune ne peut pas donner sans compromis : des assertions réutilisables, importables à la demande, sans jamais entrer en concurrence avec l'héritage déjà occupé par `WP_UnitTestCase`. Pour une agence qui gère plusieurs projets avec des vérifications récurrentes, c'est le mécanisme le plus simple à mettre en place et le moins coûteux à faire évoluer.
