Introduction : pourquoi tester en PHP ?
Développer en PHP ne se résume pas à écrire du code qui « fonctionne ». Dans le développement web moderne, la stabilité et la prévisibilité d'une application sont primordiales. Sans test, toute modification du code risque de casser des fonctionnalités existantes, et la recherche manuelle des erreurs se transforme en une « chasse aux bugs » sans fin. C'est là que les tests viennent à la rescousse.
Dans cet article, nous allons décortiquer trois niveaux clés de test pour les applications PHP : Unit (tests unitaires), Integration (tests d'intégration) et E2E (tests de bout en bout). Vous découvrirez leurs différences, les outils à utiliser et comment construire une stratégie de test efficace pour votre projet.
Tests Unitaires : la base de la pyramide
Les tests unitaires sont le niveau de test le plus rapide et le plus isolé. Leur objectif est de vérifier le fonctionnement d'une « unité » individuelle (classe, méthode ou fonction) indépendamment des dépendances externes : base de données, système de fichiers, API.
Outils pour PHP
Le framework le plus populaire est PHPUnit. Une alternative plus moderne est Pest, qui propose une syntaxe plus concise.
Exemple de test unitaire avec PHPUnit
Supposons que nous ayons une simple classe calculatrice :
<?php
// src/Calculator.php
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
public function divide(int $a, int $b): float
{
if ($b === 0) {
throw new \InvalidArgumentException('Division by zero');
}
return $a / $b;
}
}
Le test correspondant ressemblerait à ceci :
<?php
// tests/Unit/CalculatorTest.php
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
private Calculator $calculator;
protected function setUp(): void
{
$this->calculator = new Calculator();
}
public function testAddReturnsCorrectSum(): void
{
$result = $this->calculator->add(2, 3);
$this->assertEquals(5, $result);
}
public function testDivideThrowsExceptionOnZero(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->calculator->divide(10, 0);
}
}
Principes clés des tests unitaires :
- Isolation : toutes les dépendances externes (BDD, API) sont remplacées par des mock (simulateurs) ou des stub (bouchons).
- Vitesse : ils s'exécutent en quelques millisecondes.
- Ils vérifient un scénario spécifique (un assert par test est une bonne pratique).
Exécution : ./vendor/bin/phpunit tests/Unit
Tests d'Intégration : vérifier les interactions
Les tests d'intégration montent d'un niveau. Ils vérifient comment différentes parties du système fonctionnent ensemble : une classe avec une base de données, un contrôleur avec un modèle, un service avec une API externe (réelle ou simulée).
Quand utiliser les tests d'intégration ?
- Vérifier la justesse des requêtes SQL.
- Valider les liens entre les couches de l'application (ex : Repository + Entity).
- Tester les middleware ou les événements (events).
Exemple : tester un repository avec une vraie BDD
Pour les tests d'intégration, on utilise souvent une base de données de test (SQLite en mémoire ou transactions avec PostgreSQL). Voici un exemple avec PHPUnit et un trait pour réinitialiser la BDD :
<?php
// tests/Integration/UserRepositoryTest.php
use PHPUnit\Framework\TestCase;
class UserRepositoryTest extends TestCase
{
private PDO $pdo;
private UserRepository $repository;
protected function setUp(): void
{
// Création d'une SQLite en mémoire pour l'isolation
$this->pdo = new PDO('sqlite::memory:');
$this->pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');