Garantir la qualité de votre code
PHPUnit âą Tests Unitaires âą Tests Fonctionnels âą TDD âą Coverage
Formation DWWM - 2026
L'importance des tests
Les tests = contrÎle qualité dans une usine automobile :
Sans tests = bugs en production. Avec tests = code fiable ! đŻ
Un bug en production coûte 10x plus cher qu'en développement !
Les tests détectent les bugs AVANT la production !
| 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 |
Unitaires, Fonctionnels, Intégration
đĄ Principe :
Beaucoup de tests unitaires (base), moins de tests fonctionnels, trĂšs peu de E2E.
Test unitaire = Teste UNE fonction/mĂ©thode de maniĂšre ISOLĂE.
Test unitaire = goûter le sel tout seul :
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);
}
}
Test fonctionnel = Teste l'application COMPLĂTE via HTTP.
Test fonctionnel = goûter le plat fini :
class ArticleControllerTest extends WebTestCase
{
public function testListArticles(): void
{
$client = static::createClient();
$client->request('GET', '/articles');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Articles');
}
}
Framework de tests PHP
composer require --dev symfony/test-pack
tests/
âââ Unit/
âââ Functional/
âââ bootstrap.php
phpunit.xml.dist
php bin/phpunit
<?php
namespace App\Service;
class PriceCalculator
{
public function calculateTTC(float $ht): float
{
return $ht * 1.20;
}
}
<?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);
}
}
Vérifier les résultats
| 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 |
Tester les contrĂŽleurs
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'));
}
}
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éé');
}
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]);
}
Simuler des dépendances
Mock = Faux objet qui simule le comportement d'un vrai objet.
Un mock = doublure d'acteur :
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 = Données de test pré-définies.
composer require --dev doctrine/doctrine-fixtures-bundle
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();
}
}
Mesurer la couverture de code
Code Coverage = Pourcentage de code exécuté par les tests.
# Rapport HTML
php bin/phpunit --coverage-html var/coverage
# Rapport texte
php bin/phpunit --coverage-text
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.
Test-Driven Development
TDD = Ăcrire les tests AVANT le code.
public function testSlugify(): void
{
$slugger = new Slugger(); // n'existe pas encore
$result = $slugger->slugify('Hello World!');
$this->assertEquals('hello-world', $result);
}
class Slugger
{
public function slugify(string $text): string
{
return strtolower(str_replace(' ', '-', trim($text, '!')));
}
}
public function slugify(string $text): string
{
$slug = strtolower($text);
$slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
return trim($slug, '-');
}
Ăcrire de bons tests
testUserCannotLoginWithWrongPassword()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);
}
| 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 |
| 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 |
đ https://phpunit.de
đ https://symfony.com/doc/current/testing.html
đ "Test Driven Development: By Example" - Kent Beck
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