đ§Ș Tests et plan de tests
Ce guide vous accompagne dans la création d'un plan de tests et la mise en place de tests automatisés pour votre projet.
Notez qu'il s'agit plus d'un récapitulatif et d'une base pour votre plan de tests, pour les notions techniques plus complÚtes sur les tests automatisés, je vous réfÚre aux notes de cours de Web 4:
https://bentremblay.dev/4w1-h25/apprentissage/tests/intro/
ActivitĂ© en classeâ
đ AccĂ©dez Ă l'application d'exemple pour l'activitĂ© en classe
CrĂ©er un plan de tests efficaceâ
Votre premiÚre étape est de créer un plan de tests afin de déterminer ce que vous testerez et comment le tester!
Bonnes pratiquesâ
Un plan de tests bien structuré vous permet de prioriser vos efforts et d'assurer une couverture adéquate de votre application.
Commencez par identifier les fonctionnalités critiques. Ce sont les fonctionnalités essentielles au bon fonctionnement de votre application: authentification, paiements, soumission de formulaires pour les fonctionnalités critiques (ex.: création d'un produit dans une application de gestion e-commerce), etc.
Documentez vos tests. Chaque test devrait avoir un nom descriptif qui explique ce qu'il vérifie. Un développeur devrait comprendre l'objectif du test sans lire son implémentation.
Adoptez une approche progressive. Vous n'avez pas besoin de tout tester immédiatement. Commencez par les cas les plus importants et ajoutez des tests au fur et à mesure.
Comment choisir les fonctionnalitĂ©s Ă testerâ
Priorisez selon ces critĂšres :
| Priorité | Type de fonctionnalité | Exemples |
|---|---|---|
| Haute | Fonctionnalités critiques pour à l'application | Inscription, connexion, paiement, commandes |
| Haute | Fonctionnalités manipulant des données sensibles | Gestion de profil, transactions financiÚres |
| Haute | Fonctionnalités complexes avec beaucoup de logique | Calculs, validations, algorithmes |
| Moyenne | Fonctionnalités fréquemment utilisées, mais sans impact sur le coeur de l'application | Recherche, navigation, filtres |
| Basse | Fonctionnalités simples ou peu utilisées | Pages statiques, préférences visuelles |
Posez-vous la question: « Si cette fonctionnalité est brisée, quel est l'impact sur les utilisateurs? » Plus l'impact est grand, plus la priorité de test est élevée.
Exemple de plan de testsâ
Voici un exemple de tableau pour organiser votre plan de tests pour une application de commerce en ligne:
| Fonctionnalité | Scénario de test | Type | Frontend/Backend | Priorité | Responsable | Statut |
|---|---|---|---|---|---|---|
| Authentification | ||||||
| Connexion | Identifiants valides â redirection dashboard | IntĂ©gration | Backend | Haute | Claire Cliche | ComplĂ©tĂ© |
| Connexion | Mot de passe invalide â message d'erreur | IntĂ©gration | Backend | Haute | Claire Cliche | ComplĂ©tĂ© |
| Connexion | Compte inexistant â message d'erreur | IntĂ©gration | Backend | Haute | Bobby Bonzini | En cours |
| Connexion | Tentatives multiples â verrouillage compte | IntĂ©gration | Backend | Haute | Bobby Bonzini | Ă faire |
| Inscription | DonnĂ©es valides â crĂ©ation compte | IntĂ©gration | Backend | Haute | Claire Cliche | ComplĂ©tĂ© |
| Inscription | Email dĂ©jĂ utilisĂ© â erreur | IntĂ©gration | Backend | Haute | Bobby Bonzini | ComplĂ©tĂ© |
| Inscription | Mot de passe trop faible â erreur | IntĂ©gration | Backend | Moyenne | Bobby Bonzini | ComplĂ©tĂ© |
| Catalogue | ||||||
| Recherche | Terme existant â affiche rĂ©sultats | IntĂ©gration | Backend | Moyenne | Lucien Lupien | ComplĂ©tĂ© |
| Recherche | Aucun rĂ©sultat â message appropriĂ© | IntĂ©gration | Backend | Basse | Claire Cliche | Ă faire |
| Filtres | Filtre par prix â rĂ©sultats corrects | IntĂ©gration | Backend | Moyenne | Lucien Lupien | En cours |
| Panier | ||||||
| Ajout produit | Produit valide â ajoutĂ© au panier | IntĂ©gration | Backend | Haute | Bobby Bonzini | ComplĂ©tĂ© |
| Ajout produit | QuantitĂ© > stock â erreur | IntĂ©gration | Backend | Haute | Bobby Bonzini | ComplĂ©tĂ© |
| Modification quantitĂ© | QuantitĂ© valide â total recalculĂ© | IntĂ©gration | Backend | Haute | Lucien Lupien | ComplĂ©tĂ© |
| Modification quantitĂ© | QuantitĂ© = 0 â produit retirĂ© | IntĂ©gration | Backend | Moyenne | Lucien Lupien | Ă faire |
| Commande | ||||||
| Passage commande | Parcours complet â commande créée | E2E | Frontend | Haute | Claire Cliche | En cours |
| Calcul total | Prix, taxes, rabais â total correct | Unitaire | Backend | Haute | Bobby Bonzini | ComplĂ©tĂ© |
| Validation adresse | Adresse incomplĂšte â erreur | Unitaire | Backend | Moyenne | Lucien Lupien | Ă faire |
| Validations | ||||||
| Formatage prix | Affiche le prix en devise locale | Unitaire | Frontend | Basse | Lucien Lupien | Complété |
| Validation courriel | Format invalide â retourne erreur | Unitaire | Frontend | Moyenne | Bobby Bonzini | ComplĂ©tĂ© |
| Validation tĂ©lĂ©phone | Format invalide â retourne erreur | Unitaire | Frontend | Moyenne | Bobby Bonzini | Ă faire |
| Parcours utilisateur | ||||||
| Achat complet | Recherche â panier â paiement â confirmation | E2E | Frontend | Haute | Claire Cliche | Ă faire |
Types de tests: lequel choisir?â
La pyramide des tests (thĂ©orique!)â
/\
/ \ E2E (peu nombreux, lents, coûteux)
/----\
/ \ Intégration (nombre modéré)
/--------\
/ \ Unitaires (nombreux, rapides, peu coûteux)
--------------
Cette pyramide reprĂ©sente un idĂ©al thĂ©orique. Dans la pratique, la rĂ©partition dĂ©pend du type de projet. Pour des applications principalement composĂ©es de CRUDs classiques, les tests unitaires deviennent souvent redondants puisque la logique d'affaires est minimale. Dans ce contexte, il est tout Ă fait normal d'avoir plus de tests d'intĂ©gration que de tests unitaires â ils vĂ©rifient directement que vos endpoints et votre base de donnĂ©es fonctionnent correctement ensemble.
Concentrez vos tests unitaires sur la logique d'affaires réelle (calculs, validations complexes, algorithmes) et non sur du code qui ne fait que passer des données d'une couche à l'autre.
Tests unitairesâ
Les tests unitaires vérifient le comportement d'une unité de code isolée (une méthode, une classe). Pour isoler l'unité testée de ses dépendances, on utilise des mocks.
Quand les utiliser :
- Logique d'affaires isolées dans des fonctions (calculs, validations, transformations de données)
- Méthodes utilitaires
- Algorithmes
Avantages: Rapides à exécuter, faciles à maintenir, identifient précisément la source d'un problÚme.
Le concept de Mockâ
Un mock est un objet simulé qui remplace une vraie dépendance pendant un test. Cela permet de :
- Tester une classe sans ses dépendances réelles (base de données, API externe, service de courriel)
- ContrÎler le comportement des dépendances pour tester différents scénarios
- Vérifier que votre code appelle correctement ses dépendances
Imaginez que vous testez un service qui envoie des courriels. Sans mock, chaque test enverrait un vrai courriel! Avec un mock, vous simulez l'envoi et vérifiez simplement que la méthode d'envoi a été appelée avec les bons paramÚtres.
Exemple .NET avec Mocks (xUnit + Moq)â
// Interface de la dépendance
public interface INotificationService
{
Task EnvoyerCourrielAsync(string destinataire, string sujet, string message);
}
// Classe Ă tester
public class CommandeService
{
private readonly INotificationService _notificationService;
private readonly ICommandeRepository _commandeRepository;
public CommandeService(
INotificationService notificationService,
ICommandeRepository commandeRepository)
{
_notificationService = notificationService;
_commandeRepository = commandeRepository;
}
public async Task<Commande> CreerCommandeAsync(string clientEmail, List<LigneCommande> lignes)
{
if (string.IsNullOrWhiteSpace(clientEmail))
throw new ArgumentException("Le courriel est requis");
if (lignes == null || lignes.Count == 0)
throw new ArgumentException("La commande doit contenir au moins un article");
var commande = new Commande
{
ClientEmail = clientEmail,
Lignes = lignes,
Total = lignes.Sum(l => l.PrixUnitaire * l.Quantite),
DateCreation = DateTime.UtcNow
};
await _commandeRepository.AjouterAsync(commande);
await _notificationService.EnvoyerCourrielAsync(
clientEmail,
"Confirmation de commande",
$"Votre commande de {commande.Total:C} a été reçue."
);
return commande;
}
}
// Tests unitaires avec mocks
public class CommandeServiceTests
{
private readonly Mock<INotificationService> _mockNotification;
private readonly Mock<ICommandeRepository> _mockRepository;
private readonly CommandeService _service;
public CommandeServiceTests()
{
// Création des mocks
_mockNotification = new Mock<INotificationService>();
_mockRepository = new Mock<ICommandeRepository>();
// Injection des mocks dans le service Ă tester
_service = new CommandeService(
_mockNotification.Object,
_mockRepository.Object
);
}
[Fact]
public async Task CreerCommande_DonneesValides_EnvoieCourrielConfirmation()
{
// Arrange
var email = "client@test.com";
var lignes = new List<LigneCommande>
{
new() { PrixUnitaire = 10.00m, Quantite = 2 }
};
// Act
await _service.CreerCommandeAsync(email, lignes);
// Assert - Vérifier que le courriel a été envoyé avec les bons paramÚtres
_mockNotification.Verify(
n => n.EnvoyerCourrielAsync(
email,
"Confirmation de commande",
It.Is<string>(msg => msg.Contains("20,00"))),
Times.Once);
}
[Fact]
public async Task CreerCommande_DonneesValides_SauvegardeEnBD()
{
// Arrange
var lignes = new List<LigneCommande>
{
new() { PrixUnitaire = 25.00m, Quantite = 4 }
};
// Act
await _service.CreerCommandeAsync("client@test.com", lignes);
// Assert - Vérifier que la commande a été sauvegardée
_mockRepository.Verify(
r => r.AjouterAsync(It.Is<Commande>(c => c.Total == 100.00m)),
Times.Once);
}
[Fact]
public async Task CreerCommande_ErreurEnvoi_PropageException()
{
// Arrange - Simuler une erreur d'envoi de courriel
_mockNotification
.Setup(n => n.EnvoyerCourrielAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>()))
.ThrowsAsync(new Exception("Erreur SMTP"));
var lignes = new List<LigneCommande>
{
new() { PrixUnitaire = 10.00m, Quantite = 1 }
};
// Act & Assert - L'exception devrait remonter
await Assert.ThrowsAsync<Exception>(() =>
_service.CreerCommandeAsync("client@test.com", lignes));
}
[Fact]
public async Task CreerCommande_EmailVide_LanceExceptionSansEnvoyerCourriel()
{
var lignes = new List<LigneCommande>
{
new() { PrixUnitaire = 10.00m, Quantite = 1 }
};
await Assert.ThrowsAsync<ArgumentException>(() =>
_service.CreerCommandeAsync("", lignes));
// Vérifier qu'aucun courriel n'a été envoyé
_mockNotification.Verify(
n => n.EnvoyerCourrielAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>()),
Times.Never);
}
[Fact]
public async Task CreerCommande_ListeVide_LanceException()
{
await Assert.ThrowsAsync<ArgumentException>(() =>
_service.CreerCommandeAsync("client@test.com", new List<LigneCommande>()));
}
[Fact]
public async Task CreerCommande_ListeNull_LanceException()
{
await Assert.ThrowsAsync<ArgumentException>(() =>
_service.CreerCommandeAsync("client@test.com", null));
}
}
Exemple de tests unitaires simples (sans dĂ©pendances)â
// Classe Ă tester
public class CalculateurPrix
{
public decimal CalculerTotal(decimal prixUnitaire, int quantite, decimal rabais)
{
if (prixUnitaire < 0 || quantite < 0 || rabais < 0 || rabais > 100)
throw new ArgumentException("Valeurs invalides");
var sousTotal = prixUnitaire * quantite;
var montantRabais = sousTotal * (rabais / 100);
return sousTotal - montantRabais;
}
}
// Tests unitaires
public class CalculateurPrixTests
{
private readonly CalculateurPrix _calculateur = new();
[Fact]
public void CalculerTotal_SansRabais_RetournePrixFoisQuantite()
{
var resultat = _calculateur.CalculerTotal(10.00m, 3, 0);
Assert.Equal(30.00m, resultat);
}
[Fact]
public void CalculerTotal_AvecRabais10Pourcent_RetourneTotalReduit()
{
var resultat = _calculateur.CalculerTotal(100.00m, 2, 10);
Assert.Equal(180.00m, resultat);
}
[Theory]
[InlineData(-10, 1, 0)] // Prix négatif
[InlineData(10, -1, 0)] // Quantité négative
[InlineData(10, 1, -5)] // Rabais négatif
[InlineData(10, 1, 150)] // Rabais > 100%
public void CalculerTotal_ValeursInvalides_LanceException(
decimal prix, int quantite, decimal rabais)
{
Assert.Throws<ArgumentException>(() =>
_calculateur.CalculerTotal(prix, quantite, rabais));
}
[Theory]
[InlineData(0, 5, 0, 0)] // Prix à zéro
[InlineData(10, 0, 0, 0)] // Quantité à zéro
[InlineData(100, 1, 100, 0)] // Rabais de 100%
[InlineData(0.01, 1, 0, 0.01)] // Prix minimal
public void CalculerTotal_CasLimites_RetourneResultatAttendu(
decimal prix, int quantite, decimal rabais, decimal attendu)
{
var resultat = _calculateur.CalculerTotal(prix, quantite, rabais);
Assert.Equal(attendu, resultat);
}
}
Tests paramĂ©trĂ©s avec [Theory] et [InlineData]â
xUnit permet de créer des tests paramétrés qui s'exécutent plusieurs fois avec différentes données. Cela évite de dupliquer du code pour tester plusieurs cas similaires.
| Attribut | Description |
|---|---|
[Fact] | Test simple qui s'exécute une seule fois, sans paramÚtres |
[Theory] | Test paramétré qui s'exécute plusieurs fois avec différentes données |
[InlineData(...)] | Fournis un jeu de données pour une exécution du test |
Exemple comparatif :
// SANS Theory - 4 tests quasi identiques
[Fact]
public void CalculerTotal_PrixNegatif_LanceException()
{
Assert.Throws<ArgumentException>(() => _calculateur.CalculerTotal(-10, 1, 0));
}
[Fact]
public void CalculerTotal_QuantiteNegative_LanceException()
{
Assert.Throws<ArgumentException>(() => _calculateur.CalculerTotal(10, -1, 0));
}
[Fact]
public void CalculerTotal_RabaisNegatif_LanceException()
{
Assert.Throws<ArgumentException>(() => _calculateur.CalculerTotal(10, 1, -5));
}
[Fact]
public void CalculerTotal_RabaisTropGrand_LanceException()
{
Assert.Throws<ArgumentException>(() => _calculateur.CalculerTotal(10, 1, 150));
}
// AVEC Theory - Un seul test qui s'exécute 4 fois
[Theory]
[InlineData(-10, 1, 0)] // Exécution 1 : prix=-10, quantite=1, rabais=0
[InlineData(10, -1, 0)] // Exécution 2 : prix=10, quantite=-1, rabais=0
[InlineData(10, 1, -5)] // Exécution 3 : prix=10, quantite=1, rabais=-5
[InlineData(10, 1, 150)] // Exécution 4 : prix=10, quantite=1, rabais=150
public void CalculerTotal_ValeursInvalides_LanceException(
decimal prix, int quantite, decimal rabais)
{
Assert.Throws<ArgumentException>(() =>
_calculateur.CalculerTotal(prix, quantite, rabais));
}
Dans l'explorateur de tests, chaque [InlineData] apparaßt comme un test distinct avec ses valeurs affichées, ce qui facilite l'identification des cas qui échouent.
Tests d'intĂ©grationâ
Les tests d'intégration vérifient que plusieurs composants fonctionnent correctement ensemble (API, base de données, services externes). Contrairement aux tests unitaires qui utilisent des mocks, les tests d'intégration utilisent de vraies dépendances.
Quand les utiliser:
- Endpoints d'API
- ContrĂŽleurs MVC
- Vérifier l'accÚs à la base de données
Avantages: Vérifient les interactions réelles entre composants, détectent les problÚmes de configuration et les bugs liés à la base de données.
Configuration d'une base de donnĂ©es testâ
Pour les tests d'intĂ©gration, nous utilisons une vraie base de donnĂ©es (ex.: Postgres) dĂ©diĂ©e aux tests. Cette base de donnĂ©es doit ĂȘtre rĂ©initialisĂ©e avant chaque test. En .NET, la librairie Respawn peut ĂȘtre utile afin de rĂ©initialiser la base de donnĂ©es rapidement entre chaque test, garantissant un Ă©tat propre et des tests isolĂ©s.
Exemple de tests d'intĂ©grationâ
public class ProduitsTests : WebIntegrationTestBase
{
public ProduitsTests(
ApplicationFactory application,
TestDatabaseFixture databaseFixture) : base(application, databaseFixture)
{ }
[Fact]
public async Task Obtenir_ProduitExistant_RetourneProduit()
{
// Arrange - Créer les données via le repository
var categorie = await CategorieRepository.Create(new Categorie
{
Nom = "Ălectronique"
});
var produit = await ProduitRepository.Create(new Produit
{
Nom = "Laptop",
Prix = 999.99m,
CategorieId = categorie.Id
});
// Act - Appeler l'endpoint via le client HTTP
HttpResponseMessage reponse = await Client.GetAsync($"/api/produits/{produit.Id}");
// Assert - Vérifier la réponse
reponse.EnsureSuccessStatusCode();
var produitRetourne = await reponse.Content.ReadFromJsonAsync<Produit>();
Assert.Equal("Laptop", produitRetourne.Nom);
Assert.Equal(999.99m, produitRetourne.Prix);
}
[Fact]
public async Task Obtenir_ProduitInexistant_RetourneNotFound()
{
// Act
HttpResponseMessage reponse = await Client.GetAsync("/api/produits/99999");
// Assert
Assert.Equal(HttpStatusCode.NotFound, reponse.StatusCode);
}
[Fact]
public async Task Creer_ProduitValide_SauvegardeEnBD()
{
// Arrange
var categorie = await CategorieRepository.Create(new Categorie
{
Nom = "VĂȘtements"
});
var nouveauProduit = new CreateProduitDto
{
Nom = "T-Shirt",
Prix = 29.99m,
CategorieId = categorie.Id
};
// Act
HttpResponseMessage reponse = await Client.PostAsJsonAsync("/api/produits", nouveauProduit);
// Assert - Vérifier la réponse HTTP
Assert.Equal(HttpStatusCode.Created, reponse.StatusCode);
var produitCree = await reponse.Content.ReadFromJsonAsync<Produit>();
Assert.Equal("T-Shirt", produitCree.Nom);
// Assert - Vérifier que c'est réellement en BD
var produitEnBD = await ProduitRepository.GetById(produitCree.Id);
Assert.NotNull(produitEnBD);
Assert.Equal("T-Shirt", produitEnBD.Nom);
}
[Fact]
public async Task Creer_ProduitSansNom_RetourneBadRequest()
{
// Arrange
var categorie = await CategorieRepository.Create(new Categorie
{
Nom = "Test"
});
var produitInvalide = new CreateProduitDto
{
Nom = "",
Prix = 10.00m,
CategorieId = categorie.Id
};
// Act
HttpResponseMessage reponse = await Client.PostAsJsonAsync("/api/produits", produitInvalide);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, reponse.StatusCode);
}
[Fact]
public async Task Creer_ProduitPrixNegatif_RetourneBadRequest()
{
// Arrange
var categorie = await CategorieRepository.Create(new Categorie
{
Nom = "Test"
});
var produitInvalide = new CreateProduitDto
{
Nom = "Produit",
Prix = -5.00m,
CategorieId = categorie.Id
};
// Act
HttpResponseMessage reponse = await Client.PostAsJsonAsync("/api/produits", produitInvalide);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, reponse.StatusCode);
}
}
Tests End-to-End (E2E)â
Les tests E2E simulent le comportement d'un utilisateur réel dans un navigateur.
Quand les utiliser:
- Parcours utilisateur critiques (inscription, achat, etc.)
- Parcours complets impliquant plusieurs pages
- Vérification de l'intégration frontend/backend
Avantages: Testent l'application comme un vrai utilisateur, détectent les problÚmes d'interface.
Exemple avec Playwright (.NET)â
// Installation : dotnet add package Microsoft.Playwright
// Puis : pwsh bin/Debug/net9.0/playwright.ps1 install
public class AuthenticationTests : IAsyncLifetime
{
private IPlaywright _playwright;
private IBrowser _browser;
private IPage _page;
public async Task InitializeAsync()
{
_playwright = await Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true // false pour voir le navigateur pendant le test
});
_page = await _browser.NewPageAsync();
}
public async Task DisposeAsync()
{
await _browser.CloseAsync();
_playwright.Dispose();
}
[Fact]
public async Task Connexion_IdentifiantsValides_RedirigeVersDashboard()
{
// Arrange
await _page.GotoAsync("https://localhost:5001/login");
// Act
await _page.FillAsync("[data-testid='email']", "utilisateur@test.com");
await _page.FillAsync("[data-testid='password']", "MotDePasse123!");
await _page.ClickAsync("[data-testid='submit-login']");
// Assert
await _page.WaitForURLAsync("**/dashboard");
var titre = await _page.TextContentAsync("h1");
Assert.Contains("Bienvenue", titre);
}
[Fact]
public async Task Connexion_MotDePasseInvalide_AfficheErreur()
{
await _page.GotoAsync("https://localhost:5001/login");
await _page.FillAsync("[data-testid='email']", "utilisateur@test.com");
await _page.FillAsync("[data-testid='password']", "mauvais_mdp");
await _page.ClickAsync("[data-testid='submit-login']");
var erreur = await _page.TextContentAsync("[data-testid='error-message']");
Assert.Contains("Identifiants invalides", erreur);
}
[Fact]
public async Task Inscription_FormulaireComplet_CreeCompteEtConnecte()
{
await _page.GotoAsync("https://localhost:5001/inscription");
// Remplir le formulaire
await _page.FillAsync("[data-testid='nom']", "Jean Test");
await _page.FillAsync("[data-testid='email']", $"test_{Guid.NewGuid()}@test.com");
await _page.FillAsync("[data-testid='password']", "MotDePasse123!");
await _page.FillAsync("[data-testid='confirm-password']", "MotDePasse123!");
// Soumettre
await _page.ClickAsync("[data-testid='submit-inscription']");
// Vérifier la redirection et le message de succÚs
await _page.WaitForURLAsync("**/dashboard");
var notification = await _page.TextContentAsync("[data-testid='notification']");
Assert.Contains("Compte créé avec succÚs", notification);
}
}
L'importance des cas invalides et limitesâ
Un test qui vĂ©rifie uniquement le cas oĂč tout fonctionne parfaitement ne dĂ©tectera pas les bugs les plus frĂ©quents. Les vrais problĂšmes surviennent avec des donnĂ©es invalides ou aux limites.
Qu'est-ce qu'un cas limite?â
Un cas limite (edge case) teste les valeurs aux frontiĂšres du comportement attendu:
- Valeurs minimales et maximales
- Listes vides ou avec un seul élément
- ChaĂźnes vides ou trĂšs longues
- Zéro, nombres négatifs, valeurs nulles
Qu'est-ce qu'un cas invalide?â
Un cas invalide teste des entrées que votre systÚme devrait rejeter:
- Format incorrect (email sans @, date invalide)
- Valeurs hors limites (ùge de 200 ans, quantité négative)
- Données manquantes (champs obligatoires vides)
- Types incorrects (texte au lieu d'un nombre)
Un test = plusieurs scĂ©nariosâ
Une fonctionnalité nécessite habituellement plusieurs tests pour les cas invalides et limites, regroupés ensemble:
public class ValidationEmailTests
{
private readonly ValidateurEmail _validateur = new();
// Cas valides
[Theory]
[InlineData("test@example.com")]
[InlineData("prenom.nom@entreprise.ca")]
[InlineData("user+tag@gmail.com")]
public void EstValide_EmailValide_RetourneTrue(string email)
{
Assert.True(_validateur.EstValide(email));
}
// Cas invalides - format incorrect
[Theory]
[InlineData("pas_un_email")]
[InlineData("@manque_debut.com")]
[InlineData("manque_fin@")]
[InlineData("deux@@arobase.com")]
public void EstValide_FormatInvalide_RetourneFalse(string email)
{
Assert.False(_validateur.EstValide(email));
}
// Cas limites
[Theory]
[InlineData("")] // Vide
[InlineData(" ")] // Espaces seulement
[InlineData(null)] // Null
public void EstValide_ValeurVideOuNull_RetourneFalse(string email)
{
Assert.False(_validateur.EstValide(email));
}
}
RĂ©sumĂ©â
- Commencez par les tests unitaires lorsque pertinent pour la logique d'affaires â ils sont rapides et utilisent des mocks pour isoler le code testĂ©.
- Ajoutez des tests d'intégration pour vos endpoints d'API et accÚs aux données avec une vraie base de données de test.
- RĂ©servez les tests E2E aux parcours utilisateur critiques â ils sont plus lents et fragiles.
- Testez les cas invalides et limites â c'est lĂ que se cachent bien souvent les vrais problĂšmes.
- Documentez votre plan de tests â utilisez un tableau pour suivre votre progression.
- Maintenez vos tests Ă jour â un test non fiable est pire que pas de test.