Tests d'intégration et E2E¶
Tester les interfaces entre composants, les API et les parcours utilisateur de bout en bout.
Tests d'intégration¶
Un test d'intégration vérifié que plusieurs composants fonctionnent correctement ensemble. La ou le test unitaire isole une fonction, le test d'intégration teste les frontieres.
graph LR
subgraph "Test unitaire"
A["Fonction A"]
end
subgraph "Test d'integration"
B["Service"] --> C["Base de donnees"]
B --> D["Cache"]
end
subgraph "Test E2E"
E["Navigateur"] --> F["API"] --> G["DB"]
end Quoi tester en intégration¶
| Frontiere | Ce qu'on vérifié |
|---|---|
| Service → Base de données | Les requêtes SQL fonctionnent, les migrations sont correctes |
| Service → Service | Les contrats d'API sont respectes |
| Service → File de messages | Les messages sont serialises/deserialises correctement |
| Service → Cache | Les clés, les TTL, l'invalidation |
Testcontainers — environnements éphémères¶
Au lieu de mocker la base de données, lancez-en une vraie dans un conteneur :
# Python avec testcontainers
from testcontainers.postgres import PostgresContainer
def test_user_repository():
with PostgresContainer("postgres:16") as pg:
engine = create_engine(pg.get_connection_url())
repo = UserRepository(engine)
repo.create(User(name="Alice", email="alice@example.com"))
user = repo.find_by_email("alice@example.com")
assert user.name == "Alice"
// Java avec Testcontainers
@Testcontainers
class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:16");
@Test
void shouldCreateAndFindUser() {
var repo = new UserRepository(pg.getJdbcUrl());
repo.create(new User("Alice", "alice@example.com"));
var user = repo.findByEmail("alice@example.com");
assertEquals("Alice", user.getName());
}
}
Pourquoi pas de mocks pour la DB ?
Un mock de base de données ne détecté pas les erreurs SQL, les problèmes de migration, ni les différences de comportement entre moteurs. Testcontainers lance un vrai PostgreSQL/MySQL/Redis en quelques secondes — la confiance est incomparable.
Tests de contrats¶
Les tests de contrats verifient que le contrat d'interface entre un consommateur et un fournisseur est respecte, sans que les deux soient déployés ensemble.
graph LR
C["Consommateur<br/>Frontend"] -->|"contrat"| P["Fournisseur<br/>API Backend"]
C -->|"genere"| CT["Contract Test<br/>(cote consommateur)"]
CT -->|"verifie contre"| P Consumer-Driven Contracts avec Pact¶
- Le consommateur définit ses attentes (ce qu'il appelle, ce qu'il attend)
- Pact généré un fichier de contrat (JSON)
- Le fournisseur exécuté le contrat contre son code
- Si le contrat passe, les deux sont compatibles
# Cote consommateur (Python)
from pact import Consumer, Provider
pact = Consumer('Frontend').has_pact_with(Provider('UserAPI'))
(pact
.given('un utilisateur Alice existe')
.upon_receiving('une requete GET /users/alice')
.with_request('get', '/users/alice')
.will_respond_with(200, body={
'name': 'Alice',
'email': 'alice@example.com'
}))
with pact:
result = get_user('alice') # appelle le mock Pact
assert result['name'] == 'Alice'
API Testing¶
Les tests d'API verifient le comportement de vos endpoints HTTP sans passer par l'interface utilisateur.
Avec le framework de test¶
# FastAPI + pytest
from fastapi.testclient import TestClient
def test_create_user(client: TestClient):
response = client.post("/users", json={
"name": "Alice",
"email": "alice@example.com"
})
assert response.status_code == 201
assert response.json()["name"] == "Alice"
def test_create_user_duplicate_email(client: TestClient):
client.post("/users", json={"name": "Alice", "email": "alice@example.com"})
response = client.post("/users", json={"name": "Bob", "email": "alice@example.com"})
assert response.status_code == 409
Avec des outils dédiés¶
| Outil | Usage |
|---|---|
| Postman/Newman | Collections de tests API avec assertions et variables |
| Bruno | Alternative open source et Git-friendly a Postman |
| Hurl | Fichiers texte HTTP executables en CI |
| REST Client | Extension VSCode pour tester directement dans l'éditeur |
Tests E2E¶
Les tests E2E simulent un utilisateur réel : navigateur, clics, saisie de texte, navigation entre pages.
Avec Playwright¶
# Python Playwright
from playwright.sync_api import Page
def test_login_flow(page: Page):
page.goto("/login")
page.fill("#email", "alice@example.com")
page.fill("#password", "P@ssw0rd!")
page.click("#submit")
# Verification
assert page.url == "/dashboard"
assert page.text_content("h1") == "Bienvenue Alice"
// JavaScript Playwright
test('login flow', async ({ page }) => {
await page.goto('/login');
await page.fill('#email', 'alice@example.com');
await page.fill('#password', 'P@ssw0rd!');
await page.click('#submit');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toHaveText('Bienvenue Alice');
});
Bonnes pratiques E2E¶
| Pratique | Raison |
|---|---|
| Peu de tests E2E (parcours critiques) | Lents et fragiles — reservez-les au essentiel |
| Données de test isolées | Chaque test crée ses données et les nettoie |
| Selecteurs stables (data-testid) | Les classes CSS changent, les testid non |
| Retries automatiques | Les tests E2E sont inherement flaky |
| Screenshots sur échec | Diagnostic instantané en CI |
Gestion des données de test¶
| Stratégie | Description | Adapté pour |
|---|---|---|
| Fixtures | Données prédéfinies chargees avant le test | Tests deterministes |
| Factories | Generateurs d'objets avec valeurs par défaut | Tests varies |
| Seed database | Base pre-remplie pour les tests d'intégration | Suites larges |
| Ephemeral containers | Base vierge dans un conteneur par test | Isolation maximale |
Outils¶
| Outil | Type | Lien |
|---|---|---|
| Playwright | E2E multi-navigateur | playwright.dev |
| Cypress | E2E JavaScript | cypress.io |
| Testcontainers | Conteneurs éphémères | testcontainers.com |
| Pact | Contract testing | pact.io |
| Postman/Newman | API testing | postman.com |
| Bruno | API testing Git-friendly | usebruno.com |
| Hurl | HTTP testing CLI | hurl.dev |