Différences entre Test Unitaire, d'Intégration et E2E
Découvrez les différences entre tests unitaires, d'intégration et E2E. Optimisez votre code et fiabilisez vos applications grâce à une stratégie de tests adaptée.
Dans le monde du développement, les tests sont essentiels pour garantir la qualité, la stabilité et la sécurité du code. Pourtant, avec tant de types de tests disponibles, il est facile de se perdre ! L’objectif de cet article est de vous initier aux 3 types de tests fonctionnels les plus répandus en développement web : test unitaire, test d’intégration et test E2E (End-to-End).
Pourquoi écrire des tests ?
Dans le développement web, les tests jouent un rôle crucial dans l’assurance qualité des applications.
Ils permettent de vérifier que le code fonctionne correctement lorsqu’il est mis à jour. Ces tests sont l’unique garantie de la stabilité d’une application et sont donc d’incontournables alliés pour la maintenance.
La mise en place de tests, qu’ils soient unitaires, d’intégration ou E2E, est essentielle pour éviter les bugs en production et fournir une expérience utilisateur fiable.
Bien que la mise en place d’un workflow de tests puisse sembler chronophage, outre la limitation des risques d’erreurs critiques, établir une stratégie de tests bien pensée contribue sur le long terme, à gagner du temps.
3 principaux types de tests
Lorsqu’il s’agit de vérifier la robustesse d’une application, trois types de tests se démarquent : les tests unitaires, les tests d’intégration et les tests E2E.
Explorons les caractéristiques de ces types de tests, accompagnées d’exemples concrets pour appréhender leur application dans vos projets.
Test unitaire
Rôle
Le rôle des tests unitaires est de vérifier le fonctionnement de chaque fonction ou méthode de manière isolée pour s’assurer qu’elles produisent le résultat attendu, sans influence extérieure.
Les tests unitaires ciblent une logique précise et simple, ce qui aide à identifier rapidement les sources d’erreur dans le code.
Les tests unitaires sont réalisés directement dans le code.
En général, chaque test est indépendant, ce qui garantit que les modifications ou erreurs dans une fonction n’impactent pas les autres.
Exemple concret
Prenons l’exemple d’une fonction calculant le prix d’un café, auquel il est possible d’ajouter des suppléments.
Voici les prix fixés par supplément :
- noisette :
0.50 €
- caramel :
1.00 €
- chantilly :
1.00 €
Considérons la fonction calculerPrixCafe()
suivante :
FONCTION calculerPrixCafe(prixBase, supplements)
// Tableau associatif des prix des suppléments
PRIX_SUPPLEMENTS = {
"noisette" : 0.5,
"caramel" : 1,
"chantilly" : 1,
}
// Vérifier que prixBase est un nombre positif
SI TYPE_DE(prixBase) != "nombre" OU prixBase <= 0 ALORS
RETOURNER "Erreur : le prix de base doit être un nombre positif."
FIN SI
// Vérifier que supplements est un tableau
SI TYPE_DE(supplements) != "tableau" ALORS
RETOURNER "Erreur : supplements doit être un tableau."
FIN SI
// Vérifier que chaque élément du tableau est un supplément valide
POUR CHAQUE supplement DANS supplements FAIRE
SI supplement PAS_DANS PRIX_SUPPLEMENTS ALORS
RETOURNER "Erreur : supplément inconnu - " + supplement
FIN SI
FIN POUR
prixTotal = prixBase
// Calcul du prix des suppléments
POUR CHAQUE supplement DANS supplements FAIRE
SI supplement DANS PRIX_SUPPLEMENTS ALORS
prixTotal = prixTotal + PRIX_SUPPLEMENTS[supplement]
SINON
AFFICHER "Supplément inconnu : " + supplement
FIN SI
FIN POUR
RETOURNER prixTotal
FIN FONCTION
Un test unitaire doit donc vérifier que, pour une valeur donnée, la fonction calcule bien le prix du café. On isole donc cette fonction et on simule des entrées pour observer si elle produit les valeurs attendues.
Test unitaire calculerPrixCafe()
:
// 🧪 Test unitaire : sans supplément
VERIFIER(calculerPrixCafe(2) = 2)
// 🧪 Test unitaire : avec suppléments
VERIFIER(calculerPrixCafe(2, ["noisette"]) = 2.5)
VERIFIER(calculerPrixCafe(2, ["caramel"]) = 3)
VERIFIER(calculerPrixCafe(2, ["chantilly"]) = 3)
VERIFIER(calculerPrixCafe(2, ["noisette", "chantilly"]) = 3.5)
// 🧪 Tests unitaires : entrées invalides
VERIFIER(calculerPrixCafe(-1) = "Erreur : le prix de base doit être un nombre positif.")
VERIFIER(calculerPrixCafe(0) = "Erreur : le prix de base doit être un nombre positif.")
VERIFIER(calculerPrixCafe("50") = "Erreur : le prix de base doit être un nombre positif.")
VERIFIER(calculerPrixCafe(null) = "Erreur : le prix de base doit être un nombre positif.")
VERIFIER(calculerPrixCafe(2, "wasabi") = "Erreur : supplements doit être un tableau.")
VERIFIER(calculerPrixCafe(2, ["wasabi"]) = "Erreur : supplément inconnu - wasabi")
Outils de tests unitaires
Les outils permettant de faciliter l’intégration de tests unitaires au sein d’une application sont nommés frameworks de tests. Parmi les plus célèbres d’entre eux, on retrouve notamment :
Ces frameworks permettent d’écrire des tests rapides et simples à intégrer dans un pipeline CI/CD.
Test d’intégration
Rôle
Le rôle des tests d’intégration est de valider l’interaction entre plusieurs modules ou composants d’une application pour s’assurer qu’ils fonctionnent bien ensemble.
Contrairement aux tests unitaires, qui isolent chaque fonction ou méthode, les tests d’intégration vérifient la communication et la compatibilité entre différentes parties du code.
Les tests d’intégration aident à identifier les erreurs de coordination, comme les problèmes d’interaction entre composants. Ces tests sont réalisés sur des systèmes où plusieurs modules (fonctions, base de données, APIs…) sont connectés et dépendent les uns des autres.
Exemple concret
Reprenons l’exemple de notre interface permettant de commander plusieurs cafés, mais cette fois-ci, intéressons-nous à la mécanique de commande dans sa globalité, impliquant de :
- Calculer le prix des cafés
- Mettre à jour les stocks
Considérons la fonction commanderBoissons()
suivante :
FONCTION commanderBoissons(boissons)
total = 0
POUR CHAQUE boisson DANS boissons FAIRE
// Vérifier le stock de la boisson
SI stock >= 1 ALORS
// Ajouter le prix de la boisson au total
total = total + calculerPrix(boisson.base, boisson.supplement)
// Réduire le stock de la boisson
stock = stock - 1
SINON
// Gérer le cas où le stock est insuffisant
AFFICHER "Stock insuffisant pour " + boisson.nom
FIN SI
FIN POUR
RETOURNER total
FIN FONCTION
Test d’intégration : commanderBoissons()
:
boissons = [
{ base: 2 },
{ base: 2, supplement: ["noisette", "chantilly"] }
]
stock = 3
montant = commanderBoisson(boissons)
// 🧪 Test d'intégration
VERIFIER(
montant = 5.5 ET
stock = 1
);
Il ne s’agit pas d’un test unitaire mais bien d’un test d’intégration car c’est la mécanique de commande qui est testée dans son ensemble. Le test vérifie que les différentes unités ou modules du système interagissent correctement.
commanderBoissons()
: Cette fonction gère la commande de boissons, vérifie le stock, calcule le prix total, et réduit le stock.calculerPrix()
: Cette fonction est appelée pour calculer le prix de chaque boisson.- Variable
stock
: Cette variable contient le stock de boissons disponibles.
Ainsi, ce test d’intégration permet de vérifier plusieurs mécanismes :
- Interactions entre composants : Le test vérifie que la fonction
commanderBoissons()
interagit correctement avec la variable globalestock
et la fonctioncalculerPrix()
. - État partagé : Le test vérifie que l’état partagé
stock
est correctement mis à jour après l’exécution de la fonctioncommanderBoissons()
. - Résultat global : Le test vérifie que le montant de la commande
montant
est correct.
Outils de tests d’intégration
Au-delà de simples test unitaires, les frameworks de tests tels que Jest, Mocha, Vitest, JUnit ou encore PHPUnit proposent des fonctionnalités robustes pour la création et l’exécution de tests d’intégration.
Test E2E (End-to-End)
Rôle
Le rôle des tests E2E (« End-to-End » ou en français « de bout en bout ») est de vérifier le fonctionnement global de l’application du point de vue de l’utilisateur final.
Ces tests simulent des scénarios d’utilisation réels et vérifient que toutes les parties de l’application fonctionnent correctement ensemble. Il peut par exemple s’agir de :
- Commande d’un produit : Vérifier le parcours complet d’achat, de la recherche au paiement, pour assurer la fluidité.
- Formulaire de contact : Tester la soumission et validation du formulaire pour garantir l’envoi des informations.
- Inscription et connexion : S’assurer que l’utilisateur peut créer un compte et se connecter facilement.
- Gestion du panier : Valider l’ajout, la modification et la suppression d’articles pour un processus d’achat cohérent.
- Etc.
Réaliser des tests E2E (End-to-End) implique de simuler des interactions utilisateur réelles avec l’application… autrement dit, il est nécessaire de pouvoir déclencher des actions à travers l’interface utilisateur (UI) :
- Ouvrir un navigateur / Une application
- Accéder à une URL
- Cliquer sur un élément de la page
- Ecrire dans un champ de formulaire
- Etc.
Exemple concret
Gardons l’exemple de notre interface de commande de café. Un test E2E consisterait par exemple à simuler le parcours complet de commande de café par un utilisateur.
Test E2E : Commande de café avec suppléments :
// 1. 👉 Ouverture du navigateur
ouvrirNavigateur("http://monapp.com/commander")
// 🧪 Test E2E
VERIFIER(window.open(true))
// 2. 👉 Sélectionner un café
cliquerSur("bouton-selectionner-cafe")
// 🧪 Test E2E
VERIFIER(window.contientTexte("Votre café a été ajouté au panier"))
VERIFIER(panier.contientTexte("Café x1"))
// 3. 👉 Ajouter 2 suppléments
cliquerSur("option-supplement-noisette")
cliquerSur("option-supplement-chantilly")
// 🧪 Test E2E
VERIFIER(panier.contientTexte("Options (café 1) : noisette, chantilly"))
// 4. 👉 Sélectionner un second café
cliquerSur("bouton-selectionner-cafe")
// 🧪 Test E2E
VERIFIER(window.contientTexte("Votre café a été ajouté au panier"))
VERIFIER(panier.contientTexte("Café x2"))
// 5. 👉 Finaliser la commande
cliquerSur("bouton-finaliser-commande")
// 🧪 Test E2E
VERIFIER(window.contientTexte("Merci pour votre commande"))
// 🧪 Test E2E : état final de l'application
VERIFIER(
stock == 39 &&
commande.total == 5.5 &&
commande.items.contient("noisette", "chantilly")
)
Outils de tests E2E
Parmi les plus célèbres outils permettant d’exécuter des tests E2E, on retrouve notamment :
Ces outils permettent d’écrire des tests E2E qui simulent des interactions utilisateur réelles et vérifient que l’application fonctionne correctement de bout en bout.
Comparatif : Test Unitaire VS Test d’Intégration VS Test E2E
Les tests jouent chacun un rôle clé pour garantir la qualité et la robustesse des applications. Voici un tableau comparatif mettant en lumière les caractéristiques de chacun de ces types de tests :
Critères | Test unitaire | Test d’intégration | Test E2E |
---|---|---|---|
Environnement | Code (exécution isolée d’une fonction) | Code (exécution d’interaction entre modules) | Environnement utilisateur (navigateur, app mobile…) |
Exemples d’outils | Jest, Vitest, Mocha, JUnit, PHPUnit | Jest, Vitest, Mocha, JUnit, PHPUnit | Cypress, Selenium, Playwright |
Mise en place | Rapide | Moyenne - nécessite des dépendances contrôlées | Plus longue - configuration réaliste complexe |
Réalisme | Bas - teste des éléments isolés | Moyen - teste des interactions spécifiques | Élevé - simule des scénarios réels |
Isolation | Très isolé (composants individuels) | Partiellement isolé (groupes de modules) | Non isolé - système complet |
En résumé, les tests unitaires vérifient les composants individuels, les tests d’intégration valident les interactions entre modules, et les tests E2E assurent que les parcours utilisateur complets fonctionnent de bout en bout.