Test en C# : des tests unitaires aux tests E2E — comment construire une architecture de vérification de qualité dans .NET
La qualité du logiciel dépend directement du système de test. Dans l'écosystème C# et .NET, il existe une pyramide de test bien établie : les tests unitaires, les tests d'intégration et les tests de bout en bout (end-to-end). Dans cet article, nous allons décortiquer chaque niveau avec des exemples pratiques, montrer quelles bibliothèques utiliser et fournir des modèles de code prêts à l'emploi.
1. Tests unitaires : le fondement de la stabilité du code
Les tests unitaires vérifient le fonctionnement de méthodes, classes ou composants individuels, isolés des dépendances externes (BDD, API, système de fichiers). L'objectif principal est de détecter rapidement les régressions lors du refactoring.
Outils pour C#
- NUnit — framework classique avec les attributs
[Test],[TestCase]. - xUnit — framework moderne créé par les auteurs de NUnit, préféré dans les nouveaux projets.
- Moq — bibliothèque pour créer des objets mock.
- FluentAssertions — pour des assertions lisibles.
Exemple de test unitaire avec xUnit et Moq
Supposons que nous ayons un service de calcul de remise qui interroge un référentiel d'utilisateurs :
public interface IUserRepository{ bool IsPremiumUser(int userId);}
public class DiscountService{ private readonly IUserRepository _userRepo; public DiscountService(IUserRepository userRepo) => _userRepo = userRepo;
public decimal CalculateDiscount(int userId, decimal amount) { if (_userRepo.IsPremiumUser(userId)) return amount * 0.9m; // 10% de remise return amount; }}
// Testpublic class DiscountServiceTests{ [Fact] public void CalculateDiscount_ForPremiumUser_Returns90Percent() { // Arrange var mockRepo = new Mock<IUserRepository>(); mockRepo.Setup(r => r.IsPremiumUser(It.IsAny<int>())).Returns(true); var service = new DiscountService(mockRepo.Object);
// Act var result = service.CalculateDiscount(1, 100m);
// Assert Assert.Equal(90m, result); }}Conseil : Utilisez [Theory] et [InlineData] dans xUnit pour les tests paramétrés — cela réduit la duplication de code.
2. Tests d'intégration : vérification de l'interaction des composants
Les tests d'intégration vérifient comment vos modules interagissent avec des systèmes externes réels : bases de données, files d'attente, services web. Contrairement aux tests unitaires, on n'utilise pas de mocks — on déploie de véritables environnements.
Bibliothèques et approches
- TestContainers — lance des conteneurs Docker (PostgreSQL, SQL Server, Redis) directement depuis les tests.
- WebApplicationFactory (de Microsoft.AspNetCore.Mvc.Testing) — crée un hôte de test ASP.NET Core.
- Respawn — nettoie la BDD entre les tests sans recréer le conteneur.
Exemple de test d'intégration avec TestContainers et WebApplicationFactory
Vérifions que notre API enregistre correctement un utilisateur dans PostgreSQL :
public class UserApiIntegrationTests : IClassFixture<TestContainersFixture>{ private readonly HttpClient _client; private readonly TestContainersFixture _fixture;
public UserApiIntegrationTests(TestContainersFixture fixture) { _fixture = fixture; var factory = new WebApplicationFactory<Program>() .WithWebHostBuilder(builder => { builder.ConfigureTestServices(services => { // Remplace la chaîne de connexion réelle par le conteneur services.RemoveAll<DbContextOptions<AppDbContext>>(); se