đŸ§Ș Tests avec Symfony

Garantir la qualité de votre code

PHPUnit ‱ Tests Unitaires ‱ Tests Fonctionnels ‱ TDD ‱ Coverage

Formation DWWM - 2026

Au programme 📋

# 🎯 Partie 1

Pourquoi Tester ?

L'importance des tests

Pourquoi Ă©crire des tests ? đŸ€”

Analogie : Le contrĂŽle qualitĂ© en usine 🏭

Les tests = contrÎle qualité dans une usine automobile :

Sans tests = bugs en production. Avec tests = code fiable ! 🎯

ScĂ©nario SANS tests đŸ˜±

❌ Ce qui se passe sans tests :

  1. Tu écris du code
  2. Tu testes manuellement dans le navigateur
  3. Ça marche, tu commit
  4. Tu ajoutes une nouvelle fonctionnalité
  5. đŸ’„ L'ancienne fonctionnalitĂ© casse (rĂ©gression)
  6. Tu ne t'en rends pas compte
  7. Déploiement en production
  8. đŸ’„ Bug dĂ©couvert par les clients
  9. Stress, correction urgente, clients mécontents

Un bug en production coûte 10x plus cher qu'en développement !

ScĂ©nario AVEC tests ✅

✅ Ce qui se passe avec tests :

  1. Tu écris un test qui définit le comportement attendu
  2. Tu écris le code pour que le test passe
  3. Tous les tests passent ✅
  4. Tu commit
  5. Tu ajoutes une nouvelle fonctionnalité
  6. Tu lances les tests
  7. ❌ Un ancien test Ă©choue (rĂ©gression dĂ©tectĂ©e !)
  8. Tu corriges le problĂšme AVANT le commit
  9. Déploiement en production sereinement
  10. ✅ Aucun bug, clients satisfaits

Les tests détectent les bugs AVANT la production !

Les avantages des tests đŸ’Ș

Avantage Explication
Confiance Tu sais que ton code fonctionne
Détection rapide Les bugs sont trouvés immédiatement
Refactoring serein Tu peux modifier le code sans peur
Documentation vivante Les tests montrent comment utiliser le code
Régression évitée Les anciennes fonctionnalités ne cassent plus
Qualité du code Code testable = code bien structuré
Gain de temps Moins de debugging en production
# 🔬 Partie 2

Types de Tests

Unitaires, Fonctionnels, Intégration

La pyramide des tests đŸ”ș

┌───────────┐
│ E2E │ ← Peu, trùs lents
├────────────
│Fonctionnels│ ← Moyens, HTTP
├────────────
│IntĂ©gration │ ← Plus, BDD
├────────────
│ Unitaires │ ← Nombreux, rapides
└───────────┘

💡 Principe :

Beaucoup de tests unitaires (base), moins de tests fonctionnels, trĂšs peu de E2E.

Tests Unitaires 🧬

Test unitaire = Teste UNE fonction/mĂ©thode de maniĂšre ISOLÉE.

Analogie : Tester un ingrĂ©dient 🧂

Test unitaire = goûter le sel tout seul :

Exemple

class Calculator
{
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }
}

class CalculatorTest extends TestCase
{
    public function testAdd(): void
    {
        $calc = new Calculator();
        $result = $calc->add(2, 3);
        
        $this->assertEquals(5, $result);
    }
}

Tests Fonctionnels 🌐

Test fonctionnel = Teste l'application COMPLÈTE via HTTP.

Analogie : GoĂ»ter le plat complet đŸœïž

Test fonctionnel = goûter le plat fini :

Exemple

class ArticleControllerTest extends WebTestCase
{
    public function testListArticles(): void
    {
        $client = static::createClient();
        $client->request('GET', '/articles');
        
        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('h1', 'Articles');
    }
}
# ⚗ Partie 3

PHPUnit

Framework de tests PHP

Installation PHPUnit 📩

1. Installer

composer require --dev symfony/test-pack

2. Structure créée

tests/
├── Unit/
├── Functional/
└── bootstrap.php

phpunit.xml.dist

3. Lancer les tests

php bin/phpunit

Premier test 🚀

Classe Ă  tester

<?php
namespace App\Service;

class PriceCalculator
{
    public function calculateTTC(float $ht): float
    {
        return $ht * 1.20;
    }
}

Test

<?php
namespace App\Tests\Unit\Service;

use App\Service\PriceCalculator;
use PHPUnit\Framework\TestCase;

class PriceCalculatorTest extends TestCase
{
    public function testCalculateTTC(): void
    {
        // Arrange
        $calc = new PriceCalculator();
        
        // Act
        $result = $calc->calculateTTC(100);
        
        // Assert
        $this->assertEquals(120, $result);
    }
}
# ✅ Partie 4

Assertions

Vérifier les résultats

Assertions courantes ✅

Assertion Usage
assertEquals($expected, $actual) ÉgalitĂ© de valeur
assertSame($expected, $actual) Identité stricte (===)
assertTrue($condition) Valeur true
assertFalse($condition) Valeur false
assertNull($value) Valeur null
assertCount($n, $array) Nombre d'éléments
assertInstanceOf($class, $obj) Instance de classe
assertStringContainsString($needle, $haystack) Contient une chaĂźne
# 🌐 Partie 5

Tests Fonctionnels

Tester les contrĂŽleurs

WebTestCase 🌐

Test d'une page

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ArticleControllerTest extends WebTestCase
{
    public function testIndex(): void
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/articles');
        
        // Vérifier code HTTP
        $this->assertResponseIsSuccessful();
        
        // Vérifier contenu
        $this->assertSelectorTextContains('h1', 'Articles');
        
        // Compter éléments
        $this->assertCount(10, $crawler->filter('.article'));
    }
}

Tester un formulaire 📝

public function testCreateArticle(): void
{
    $client = static::createClient();
    $crawler = $client->request('GET', '/article/new');
    
    // Remplir le formulaire
    $form = $crawler->selectButton('Créer')->form([
        'article[title]' => 'Mon article',
        'article[content]' => 'Contenu...'
    ]);
    
    // Soumettre
    $client->submit($form);
    
    // Vérifier redirection
    $this->assertResponseRedirects('/articles');
    
    // Suivre la redirection
    $client->followRedirect();
    
    // Vérifier le message
    $this->assertSelectorTextContains('.alert-success', 'Article créé');
}

Tester une API 🌐

public function testApiGetArticles(): void
{
    $client = static::createClient();
    $client->request('GET', '/api/articles');
    
    // Vérifier code HTTP
    $this->assertResponseIsSuccessful();
    
    // Vérifier Content-Type
    $this->assertResponseHeaderSame('Content-Type', 'application/json');
    
    // Récupérer le JSON
    $data = json_decode($client->getResponse()->getContent(), true);
    
    // Vérifier structure
    $this->assertIsArray($data);
    $this->assertArrayHasKey('id', $data[0]);
    $this->assertArrayHasKey('title', $data[0]);
}
# 🎭 Partie 6

Mocking et Fixtures

Simuler des dépendances

C'est quoi le Mocking ? 🎭

Mock = Faux objet qui simule le comportement d'un vrai objet.

Analogie : La doublure de cinĂ©ma 🎬

Un mock = doublure d'acteur :

CrĂ©er un Mock 🎭

public function testSendEmail(): void
{
    // Créer un mock de MailerInterface
    $mailer = $this->createMock(MailerInterface::class);
    
    // Définir le comportement
    $mailer->expects($this->once())
           ->method('send');
    
    // Utiliser le mock
    $service = new UserService($mailer);
    $service->register('test@example.com');
    
    // Le mock vérifie que send() a été appelé 1 fois
}

Fixtures de test 🎭

Fixtures = Données de test pré-définies.

Installer

composer require --dev doctrine/doctrine-fixtures-bundle

Créer des fixtures

namespace App\DataFixtures;

use App\Entity\Article;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;

class ArticleFixtures extends Fixture
{
    public function load(ObjectManager $manager): void
    {
        for ($i = 1; $i <= 10; $i++) {
            $article = new Article();
            $article->setTitle("Article $i");
            $article->setContent("Contenu $i");
            
            $manager->persist($article);
        }
        
        $manager->flush();
    }
}
# 📊 Partie 7

Code Coverage

Mesurer la couverture de code

C'est quoi le Coverage ? 📊

Code Coverage = Pourcentage de code exécuté par les tests.

Générer le coverage

# Rapport HTML
php bin/phpunit --coverage-html var/coverage

# Rapport texte
php bin/phpunit --coverage-text

Résultat

Code Coverage Report:
  Classes: 85.00% (17/20)
  Methods: 90.00% (27/30)
  Lines:   88.50% (177/200)

💡 Objectif :

Viser 80%+ de coverage, mais 100% n'est pas toujours nécessaire.

# 🔄 Partie 8

TDD

Test-Driven Development

C'est quoi le TDD ? 🔄

TDD = Écrire les tests AVANT le code.

Cycle TDD (Red-Green-Refactor)

  1. 🔮 Red : Écrire un test qui Ă©choue
  2. 🟱 Green : Écrire le code minimum pour passer
  3. ♻ Refactor : AmĂ©liorer le code
  4. 🔁 RĂ©pĂ©ter
Red (test Ă©choue) → Green (test passe) → Refactor → Red...

Exemple TDD 🔄

1. 🔮 Test (Ă©choue)

public function testSlugify(): void
{
    $slugger = new Slugger();  // n'existe pas encore
    $result = $slugger->slugify('Hello World!');
    
    $this->assertEquals('hello-world', $result);
}

2. 🟱 Code minimum

class Slugger
{
    public function slugify(string $text): string
    {
        return strtolower(str_replace(' ', '-', trim($text, '!')));
    }
}

3. ♻ Refactor

public function slugify(string $text): string
{
    $slug = strtolower($text);
    $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
    return trim($slug, '-');
}
# 💡 Partie 9

Bonnes Pratiques

Écrire de bons tests

Bonnes pratiques 💡

  1. Tests indépendants : Chaque test doit pouvoir s'exécuter seul
  2. Un test = Une chose : Tester un seul concept par test
  3. Noms explicites : testUserCannotLoginWithWrongPassword()
  4. AAA (Arrange-Act-Assert) : Structure claire
  5. Pas de logique : Pas de if, de boucles...
  6. Tests rapides : Privilégier unitaires aux E2E
  7. Nettoyer : Utiliser tearDown()

Pattern AAA 📐

public function testCalculateTTC(): void
{
    // 🔧 ARRANGE (PrĂ©parer)
    $calculator = new PriceCalculator();
    $priceHT = 100;
    
    // ⚡ ACT (Agir)
    $result = $calculator->calculateTTC($priceHT);
    
    // ✅ ASSERT (VĂ©rifier)
    $this->assertEquals(120, $result);
}
# 🎓 RĂ©capitulatif

Ce qu'on a appris

RĂ©sumĂ© des concepts 📚

Concept Description
Test unitaire Teste une fonction isolée
Test fonctionnel Teste l'application via HTTP
PHPUnit Framework de tests PHP
Mock Faux objet qui simule une dépendance
Fixtures Données de test pré-définies
Coverage Pourcentage de code testé
TDD Écrire les tests AVANT le code

Commandes utiles đŸ› ïž

Commande Description
php bin/phpunit Lancer tous les tests
php bin/phpunit tests/Unit Tests unitaires seulement
php bin/phpunit --filter testAdd Un test spécifique
php bin/phpunit --coverage-html var/coverage Rapport coverage HTML
php bin/phpunit --testdox Format lisible

Ressources 📚

Documentation PHPUnit

🌐 https://phpunit.de

Symfony Testing

🌐 https://symfony.com/doc/current/testing.html

TDD (livre)

📖 "Test Driven Development: By Example" - Kent Beck

CompĂ©tences acquises đŸ’Ș

🎓 FĂ©licitations !

Vous maĂźtrisez les Tests avec Symfony

Vous savez maintenant :

✅ Écrire des tests unitaires et fonctionnels

✅ Garantir la qualitĂ© de votre code

✅ Pratiquer le TDD

✅ Utiliser PHPUnit professionnellement

💡 Conseil final :

Les tests sont un INVESTISSEMENT, pas une perte de temps. Un bug trouvĂ© en test = 10 minutes. Un bug en production = 10 heures. Testez votre code ! đŸ§Ș

Formation Symfony complùte ! 🚀

Formation DWWM - 2026