# Découpler la logique métier du cœur pour la rendre testable

> Une fonction qui mélange calcul métier et appels à wp_insert_post ne se teste qu'en base de données, lentement. L'extraire en classe pure change tout.

- Auteur : Clément Hadrot
- Publié le : 2022-06-27
- Mis à jour le : 2022-06-27
- Catégorie : Tests
- URL : https://wpmoderne.dev.wordpress-developpement.fr/tests/decoupler-logique-metier-la-rendre-testable/

## L’essentiel

- Séparer le calcul pur des effets de bord WordPress
- Un test unitaire pur s'exécute en millisecondes
- La classe pure devient réutilisable ailleurs dans le projet

Sur un projet de gestion de formations professionnelles, la fonction qui calcule le tarif final d'une inscription — remises, quotas, majoration tardive — appelait directement `get_post_meta()`, `wp_get_object_terms()` et `update_post_meta()` en une seule fonction de 90 lignes. La tester correctement obligeait à créer un article WordPress complet en base pour chaque scénario de tarification, soit plus d'une seconde par test rien que pour l'initialisation des fixtures. Avec une trentaine de scénarios de tarification à couvrir, la suite dédiée à cette seule fonction prenait plus d'une minute.

Le problème ne venait pas de PHPUnit ni de `WP_UnitTestCase`, mais de la conception : le calcul, une pure fonction mathématique, était inutilement couplé à la persistance WordPress.

## Repérer le mélange des responsabilités

La fonction originale ressemblait à ceci — un calcul pur noyé au milieu de lectures et d'écritures WordPress :

```
function calculer_tarif_inscription(int $inscription_id): float {
    $tarif_base = (float) get_post_meta($inscription_id, 'tarif_base', true);
    $quota_restant = (int) get_post_meta($inscription_id, 'quota_restant', true);
    $categories = wp_get_object_terms($inscription_id, 'categorie_formation');

    $remise = 0.0;
    if ($quota_restant < 5) {
        $remise = 0.10;
    }
    foreach ($categories as $categorie) {
        if ($categorie->slug === 'formation-longue') {
            $remise += 0.05;
        }
    }

    $tarif_final = $tarif_base * (1 - $remise);
    update_post_meta($inscription_id, 'tarif_calcule', $tarif_final);

    return $tarif_final;
}
```

## Extraire le calcul dans une classe pure

Le principe : une classe qui ne connaît rien de WordPress, ne reçoit que des types PHP natifs en entrée, et ne produit qu'un type natif en sortie. Aucun appel à une fonction préfixée `wp_` ou `get_post_meta` n'y figure :

```
final class Calculateur_Tarif_Inscription {

    public function calculer(float $tarif_base, int $quota_restant, array $slugs_categories): float {
        $remise = 0.0;

        if ($quota_restant < 5) {
            $remise += 0.10;
        }
        if (in_array('formation-longue', $slugs_categories, true)) {
            $remise += 0.05;
        }

        return $tarif_base * (1 - $remise);
    }
}
```

> L'essentiel à retenir : Séparer le calcul pur des effets de bord WordPress ; Un test unitaire pur s'exécute en millisecondes ; La classe pure devient réutilisable ailleurs dans le projet

## La fonction WordPress devient une simple façade

La fonction originale garde son rôle d'orchestration — lire les données, appeler le calcul, persister le résultat — mais délègue tout le raisonnement métier à la classe pure :

```
function calculer_tarif_inscription(int $inscription_id): float {
    $tarif_base = (float) get_post_meta($inscription_id, 'tarif_base', true);
    $quota_restant = (int) get_post_meta($inscription_id, 'quota_restant', true);
    $slugs = wp_list_pluck(wp_get_object_terms($inscription_id, 'categorie_formation'), 'slug');

    $tarif_final = (new Calculateur_Tarif_Inscription())->calculer($tarif_base, $quota_restant, $slugs);

    update_post_meta($inscription_id, 'tarif_calcule', $tarif_final);
    return $tarif_final;
}
```

## Le gain immédiat sur les tests

Les trente scénarios de tarification se testent désormais sans base de données, sans `WP_UnitTestCase`, avec une simple classe `TestCase` de PHPUnit standard :

```
final class Test_Calculateur_Tarif_Inscription extends \PHPUnit\Framework\TestCase {

    public function test_remise_quota_faible(): void {
        $calculateur = new Calculateur_Tarif_Inscription();

        $resultat = $calculateur->calculer(300.0, 3, []);

        $this->assertEquals(270.0, $resultat);
    }

    public function test_cumul_remise_quota_et_formation_longue(): void {
        $calculateur = new Calculateur_Tarif_Inscription();

        $resultat = $calculateur->calculer(300.0, 3, ['formation-longue']);

        $this->assertEquals(255.0, $resultat);
    }
}
```

Ces tests s'exécutent en quelques millisecondes chacun, sans transaction de base de données à ouvrir ni à annuler — un gain qui devient significatif dès que la suite dépasse quelques dizaines de scénarios.

## Arborescence type d'un projet qui applique ce pattern

```
src/
├── Domain/
│   ├── Calculateur_Tarif_Inscription.php   (logique pure, testée en millisecondes)
│   └── Regles_Quota.php
├── WordPress/
│   ├── inscriptions-hooks.php              (façade, appelle le Domain)
│   └── inscriptions-rest.php
tests/
├── Unit/
│   └── Test_Calculateur_Tarif_Inscription.php   (PHPUnit\Framework\TestCase)
└── Integration/
    └── Test_Inscription_Complete.php             (WP_UnitTestCase)
```

> Une classe qui ne peut pas être testée sans une base de données n'est presque jamais une classe qui a vraiment besoin d'une base de données — c'est souvent une classe qui n'a simplement jamais été séparée de sa persistance.

## Limites de ce pattern

Ce découplage ne concerne que la logique de calcul et de décision, jamais la persistance elle-même ni les hooks WordPress, dont le comportement reste vérifié par des tests d'intégration classiques avec base de données — un sujet traité séparément. Extraire une classe pure pour un calcul trivial d'une seule ligne n'apporte souvent aucun bénéfice ; l'effort se justifie à partir du moment où la logique comporte plusieurs branches conditionnelles qui méritent une couverture de test fine.

## En résumé

Séparer la logique métier pure de son intégration WordPress n'est pas un exercice architectural abstrait : c'est ce qui a permis, sur ce projet de formations professionnelles, de faire passer une suite de tests de tarification de plus d'une minute à moins d'une seconde, tout en rendant chaque scénario de remise plus facile à lire et à faire évoluer.
