Tests d’intégration et tests fonctionnels
Testez concrètement la façon dont les composants fonctionnent ensemble.
Tests d’intégration et tests fonctionnels est une leçon PHP Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage PHP Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours PHP Academy comprend 4 leçons au total.
Au-delà des essais unitaires
Les essais unitaires prouvent qu'une classe fonctionne isolément, avec tout le reste simulé. Mais les simulations peuvent mentir : la vraie base de données, le routeur du cadriciel ou la couche HTTP se comportent différemment. Les essais d'intégration vérifient au moins deux composants réels reliés entre eux ; les essais fonctionnels exercent l'application de bout en bout via son point d'entrée public (une requête HTTP, une commande de console). Cette leçon explique leur rôle et met les deux en œuvre.
La pyramide des essais
Une suite saine a la forme d'une pyramide :
- De nombreux essais unitaires — rapides, isolés, exécutés en permanence.
- Moins d'essais d'intégration — vraie base de données, vraie file d'attente et vrai cache, plus lents.
- Peu d'essais fonctionnels/de bout en bout — pile complète, les plus lents et les plus fragiles.
Inverser cette structure (un « cornet de glace » composé surtout d'essais de bout en bout) produit une suite lente et instable. Faites descendre la couverture autant que possible.
Intégration : le dépôt face à une vraie base de données
Un essai d'intégration d'un dépôt utilise une connexion à une base de données réelle — pas une simulation — afin de détecter les erreurs SQL, de schéma et de mappage. Utilisez une base de données d'essai dédiée (ou une instance SQLite en mémoire suffisamment proche de votre dialecte) et un vrai PDO.
<?php
use PHPUnit\Framework\TestCase;
final class UserRepositoryTest extends TestCase
{
private \PDO $pdo;
protected function setUp(): void
{
$this->pdo = new \PDO('sqlite::memory:');
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)');
}
public function test_persists_and_finds_a_user(): void
{
$repo = new UserRepository($this->pdo);
$id = $repo->create('ada@example.com');
self::assertSame('ada@example.com', $repo->find($id)->email);
}
}
Isolation : annulation de la transaction
Les essais d'intégration ne doivent pas laisser leur état fuiter d'un essai à l'autre. Le modèle le plus rapide consiste à envelopper chaque essai dans une transaction et à l'annuler dans tearDown() — la base de données retrouve un état vierge sans réamorçage. (La troncature est la solution de repli lorsque le code soumis à l'essai valide la transaction ou utilise le DDL.)
<?php
protected function setUp(): void
{
$this->pdo = TestDb::connection();
$this->pdo->beginTransaction();
}
protected function tearDown(): void
{
$this->pdo->rollBack(); // undo everything this test did
}
Jeux de données et fabriques
Les essais ont besoin de données. Évitez les énormes vidages SQL fragiles ; préférez de petites fabriques qui construisent uniquement les lignes nécessaires à un essai, avec des valeurs par défaut raisonnables que vous remplacez selon le cas. Ainsi, l'intention de chaque essai reste explicite et résiste à l'évolution du schéma.
<?php
final class UserFactory
{
public static function create(\PDO $pdo, array $overrides = []): int
{
$data = array_merge(['email' => 'user'.uniqid().'@test.dev'], $overrides);
$stmt = $pdo->prepare('INSERT INTO users (email) VALUES (:email)');
$stmt->execute($data);
return (int) $pdo->lastInsertId();
}
}
Fonctionnel : solliciter l'application comme un client
Un essai fonctionnel pilote le noyau et le routeur réels de l'application avec une requête simulée et vérifie la réponse — code d'état, en-têtes, corps. WebTestCase de Symfony et les assistants d'essai HTTP de Laravel font exactement cela sans démarrer un véritable serveur web. Voici la structure Symfony.
<?php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class HealthControllerTest extends WebTestCase
{
public function test_health_endpoint_returns_ok(): void
{
$client = static::createClient();
$client->request('GET', '/health');
self::assertResponseIsSuccessful(); // 2xx
self::assertJson($client->getResponse()->getContent());
}
}
Vérifications de la réponse
Les essais fonctionnels vérifient le comportement observable à la frontière : état, forme JSON, effets secondaires dans la base de données. Ci-dessous, un POST crée une ressource ; nous vérifions à la fois la réponse HTTP et le fait que l'enregistrement a effectivement été stocké — ce qui prouve que toute la pile a coopéré.
<?php
public function test_creating_a_user_persists_it(): void
{
$client = static::createClient();
$client->request('POST', '/users', server: [
'CONTENT_TYPE' => 'application/json',
], content: json_encode(['email' => 'ada@example.com']));
self::assertResponseStatusCodeSame(201);
$repo = static::getContainer()->get(UserRepository::class);
self::assertNotNull($repo->findByEmail('ada@example.com'));
}
Simuler sélectivement les frontières
Même les essais fonctionnels ne devraient pas appeler de vrais prestataires de paiement ni envoyer de vrais courriels. Remplacez uniquement les services externes les plus périphériques — échangez-les dans le conteneur d'essai contre des implémentations simulées ou en mémoire — tout en gardant votre propre code, le routage et la base de données réels. Vous préservez ainsi la fidélité de bout en bout sans appels réseau instables.
<?php
public function test_checkout_charges_via_gateway(): void
{
$client = static::createClient();
// Replace the real gateway binding with an in-memory fake:
static::getContainer()->set(PaymentGateway::class, new FakePaymentGateway());
$client->request('POST', '/checkout', content: json_encode(['cart' => 1]));
self::assertResponseIsSuccessful();
}
Suites distinctes, vitesses distinctes
Divisez votre phpunit.xml en suites d'essais afin que la suite unitaire rapide s'exécute à chaque enregistrement et que les suites d'intégration et fonctionnelles lentes s'exécutent à la demande ou dans l'intégration continue. Regroupez-les par répertoire et sélectionnez-les avec --testsuite.
<phpunit>
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
<testsuite name="functional">
<directory>tests/Functional</directory>
</testsuite>
</testsuites>
</phpunit>Le déterminisme prime sur la couverture
Le moyen le plus rapide de perdre confiance dans une suite est son instabilité. Rendez les essais d'intégration et fonctionnels déterministes :
- Figez le temps (injectez une horloge) au lieu d'appeler
time(). - Initialisez le générateur aléatoire ; évitez les vérifications dépendant de l'ordre sur des requêtes non triées.
- Réinitialisez l'état de la base de données à chaque essai (annulation de transaction / troncature).
- Ne dépendez jamais de l'ordre d'exécution des essais.
Aligner la base d'essai sur la production
Une base SQLite en mémoire est rapide, mais les différences de dialecte (sensibilité à la casse, fonctions JSON, vérification des clés étrangères, types) peuvent masquer des erreurs qui n'apparaissent que sur votre véritable moteur. Pour le code qui utilise du SQL propre au moteur, exécutez les essais d'intégration sur le même moteur qu'en production — généralement dans un conteneur Docker jetable en intégration continue — plutôt que d'utiliser un substitut pratique.
<?php
// Read connection from environment so CI points at a real Postgres/MySQL container
$dsn = getenv('TEST_DATABASE_URL') ?: 'sqlite::memory:';
$pdo = new \PDO($dsn);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// In CI, TEST_DATABASE_URL targets the same engine as production.
Vérification rapide
Comment un essai fonctionnel doit-il traiter un prestataire de paiement tiers ?
Récapitulatif
Vous avez appris à vérifier des composants ensemble :
- Les essais d'intégration utilisent de vraies bases de données et de vrais services pour détecter ce que les simulations masquent ; isolez-les avec l'annulation de transaction.
- Les essais fonctionnels pilotent le noyau réel via des requêtes simulées et vérifient les réponses ainsi que les effets secondaires.
- Les fabriques construisent des données minimales ; simulez uniquement les frontières externes les plus périphériques.
- Répartissez les suites selon leur vitesse et rendez les essais plus lents déterministes.
Ensuite : mesurer la qualité des essais avec la mise à l'épreuve par mutation.
Questions Fréquemment Posées
La leçon « Tests d’intégration et tests fonctionnels » est-elle gratuite ?
Oui — le texte complet de « Tests d’intégration et tests fonctionnels » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours PHP Academy, passe à CoddyKit PRO. Le cours PHP Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Tests d’intégration et tests fonctionnels » ?
Testez concrètement la façon dont les composants fonctionnent ensemble. Tu pratiques PHP Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer PHP Academy ?
Aucune expérience préalable n'est requise. PHP Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.
Combien de temps prend la leçon « Tests d’intégration et tests fonctionnels » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon PHP Academy ?
Oui. Chaque leçon PHP Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Le flux de développement piloté par les tests
- Simulations et bouchons avec Mockery
- Tests d’intégration et tests fonctionnels
- Tests de mutation avec Infection