Aller au contenu principal

Les tests : comment bien jouer au Cluedo

Comprendre enfin la différence entre Mock et Functional Test. Un guide pratique sur PHPUnit basés sur des cas réels.
Catégorie

Tests

Coder sans tests, c'est comme sauter sans parachute. Apprenez à construire une suite de tests automatisés qui protège réellement votre business contre les régressions.

Lecture
4 min
Niveau
Intermédiaire
mars 4 2025
Partager

« Une dernière chose... »

Dans un manoir sur un serveur web en production, chaque bug est un crime. Une NullPointerException a été retrouvée dans la bibliothèque. La victime ? L'expérience utilisateur.

Qui est le coupable ? Le Service ? Le Controller ? La Base de Données ? Pour le savoir, nous allons mener l'enquête avec PHPUnit. Sortez votre loupe !

L'interrogatoire : Le test unitaire

Le test unitaire, c'est l'interrogatoire du suspect dans une salle blanche, isolée, sans fenêtre.

Le suspect, c'est votre classe PHP (ici, PostNavigationService).

Pour qu'il ne puisse pas rejeter la faute sur les autres, je vais remplacer ses complices par des doublures (mocks).

La scène

Le service src/Service/Content/PostNavigationService.php doit trouver des articles similaires.

Il a deux complices :

  • MeilisearchSearchService : le moteur de recherche.
  • PostRepository : la base de données.

Dans mon test unitaire, je ne vais jamais appeler la vraie base de données ou le vrai Meilisearch, je vais simuler leur comportement.

La Preuve

PHP
// tests/Unit/Service/Content/PostNavigationServiceTest.php
public function testGetRelatedPostsUsesMeilisearchFirst(): void
{
    // 1. La Préparation (Arrange)
    // On crée les doublures. Ce sont des acteurs qui suivent un script.
    /** @var PostRepositoryInterface&MockObject $repo */
    $repo = $this->createMock(PostRepositoryInterface::class);
    
    /** @var MeilisearchSearchService&MockObject $search */
    $search = $this->createMock(MeilisearchSearchService::class);
    
    // On isole le suspect avec ses faux complices
    $service = new PostNavigationService($repo, $search);

    // 2. Le Script (Expectations)
    // On dit à l'acteur Meilisearch : "Si on t'appelle, dis que tu es disponible."
    $search->expects($this->once())
        ->method('isAvailable')
        ->willReturn(true);

    // "Et retourne ces deux articles fictifs."
    $related1 = $this->createPost('Article A');
    $related2 = $this->createPost('Article B');
    
    $search->expects($this->once())
        ->method('search')
        ->willReturn([$related1, $related2]);

    // 3. L'Action (Act)
    $results = $service->getRelatedPosts($this->createPost('Current'), 3);

    // 4. Le Verdict (Assert)
    // Le service a-t-il bien renvoyé ce que Meilisearch lui a donné ?
    $this->assertCount(2, $results);
    $this->assertSame([$related1, $related2], $results);
}

Si ce test échoue, on sait à 100% que le coupable est PostNavigationService. Ce n'est pas la faute du réseau, ni de la BDD, car ils n'étaient pas là.

La reconstitution : le test fonctionnel

Le test fonctionnel, c'est la reconstitution du crime sur les lieux.

Ici, on ne mocke (presque) rien. On démarre le vrai framework Symfony (le Kernel), la vraie base de données de test, et on simule une requête HTTP : c'est l'inspecteur qui se fait passer pour un client.

La preuve

PHP
// tests/Functional/Controller/Content/PostActionTest.php
public function testPostPageLoadsSuccessfully(): void
{
    // 1. On démarre le client (le navigateur simulé)
    $client = self::createClient();
    
    // 2. On récupère un vrai article en base de test
    // (Note: La BDD a été peuplée par des Fixtures au préalable)
    $postRepository = self::getContainer()->get(PostRepository::class);
    $post = $postRepository->findOneBy([]);

    // 3. On tente d'accéder à la page
    $client->request('GET', sprintf('/billets/%s', $post->slug));

    // 4. On vérifie que la porte s'ouvre (200 OK)
    $this->assertResponseIsSuccessful();
    
    // 5. On vérifie qu'on est au bon endroit
    $this->assertSelectorTextContains('h1', $post->title);
}

Ici, on teste l'intégration : Le Routeur -> Le Controller -> Le Service -> Twig. Si ça plante, le coupable peut être n'importe où dans la chaîne. C'est moins précis, mais plus réaliste.

La caméra de surveillance : le test E2E

Enfin, il y a le test End-to-End : c'est l'artillerie lourde.

Avec un test E2E, on piloterait un vrai navigateur (Chrome/Firefox) via une librairie comme Symfony Panther. On clique sur les vrais boutons, le JavaScript s'exécute, les CSS sont rendus.

Pourquoi ne pas l'utiliser sur ce projet ? Le coût. Un test E2E est lent (plusieurs secondes) et fragile (un changement de CSS peut tout casser). Pour un blog technique comme le mien, mes tests fonctionnels (qui simulent le navigateur sans l'interface graphique) suffisent à couvrir 99% des cas en quelques millisecondes. On réserve le FBI pour les applications riches où le JavaScript est roi.

Le mot de la fin

Une bonne stratégie de test ressemble à une pyramide :

  • Beaucoup de Tests Unitaires avec des interrogatoires : ils sont rapides (ms), précis et couvrent tous les cas tordus. C'est votre filet de sécurité quotidien.
  • Quelques Tests Fonctionnels avec des reconstitutions : ils assurent que les composants se parlent bien entre eux (Controller + Template).
  • Une poignée de Tests E2E avec des caméras : ils valident les parcours critiques (Paiement, Login).

N'attendez pas le crime pour enquêter. Soyez un détective proactif. Écrivez le test (l'accusation) avant le code (le crime). C'est le TDD (Test Driven Development ou Test Driven Detective).

0 réaction

Poursuivre la lecture

Sélectionné avec soin pour vous.

L'injection de dépendance, ou comment être fainéant avec élégance

Découvrez comment Symfony vous aide à coder sans effort avec l'injection de dépendance. Dit adieu aux 'new' et bonjour à l'autowiring!

Comment j'ai industrialisé mon side-project avec Google Jules

Ne laissez plus l'IA "deviner" si le code fonctionne. Apprenez à configurer une sandbox Docker pour Google Jules afin d'automatiser la QA, la sécurité et la performance, loin du "Vibe Coding".

Anatomie d’un projet Symfony

Explorez les mystères de Symfony avec humour acide : dossiers, outils et secrets révélés pour les développeurs curieux.