Tests d'intégration C# avec Docker : niveau avancé
Les tests d'intégration constituent une étape critique du développement en C#, permettant de vérifier l'interaction des composants système avec des dépendances réelles : bases de données, files de messages, API externes. L'utilisation de Docker à cette fin est devenue une norme industrielle, mais au niveau avancé se posent des questions d'isolation, de performance et d'exécution parallèle. Dans cet article, nous analyserons les techniques professionnelles de tests d'intégration C# avec Docker, en utilisant la bibliothèque Testcontainers et des configurations avancées.
1. Configuration de Testcontainers pour .NET : infrastructure de base
La bibliothèque Testcontainers pour .NET permet de gérer par programmation les conteneurs Docker dans l'environnement de test. Contrairement au lancement manuel de docker-compose, Testcontainers démarre et arrête automatiquement les conteneurs pour chaque classe de test ou module, garantissant un environnement propre.
Installez le package via NuGet :
dotnet add package Testcontainers --version 3.9.0dotnet add package Testcontainers.PostgreSql --version 3.9.0dotnet add package Testcontainers.MsSql --version 3.9.0Exemple de classe de base pour les tests d'intégration avec PostgreSQL :
using Testcontainers.PostgreSql;using Xunit;
public class DatabaseFixture : IAsyncLifetime{ private readonly PostgreSqlContainer _container = new PostgreSqlBuilder() .WithImage("postgres:15-alpine") .WithDatabase("testdb") .WithUsername("testuser") .WithPassword("testpass") .Build();
public string ConnectionString => _container.GetConnectionString();
public async Task InitializeAsync() { await _container.StartAsync(); }
public async Task DisposeAsync() { await _container.StopAsync(); }}Désormais, chaque classe de test peut utiliser ce fixture pour obtenir une instance de base de données isolée.
2. Isolation des tests et gestion de l'état des conteneurs
Le niveau avancé nécessite un contrôle précis de l'isolation. Au lieu d'un seul conteneur pour l'ensemble des tests, utilisez un conteneur par classe de test ou même par test. Cela évite un état « sale » de la base de données entre les tests.
Implémentation via l'attribut Collection dans xUnit :
[CollectionDefinition("Database collection")]public class DatabaseCollection : ICollectionFixture<DatabaseFixture>{}
[Collection("Database collection")]public class UserRepositoryTests{ private readonly DatabaseFixture _fixture;
public UserRepositoryTests(DatabaseFixture fixture) { _fixture = fixture; }
[Fact] public async Task CreateUser_ShouldPersistUser() { // Arrange await using var context = new AppDbContext(_fixture.ConnectionString); await context.Database.EnsureCreatedAsync(); var repo = new UserRepository(context);
// Act var user = new User { Name = "Test User" }; await repo.CreateAsync(user);
// Assert var savedUser = await context.Users.FindAsync(user.Id); Assert.NotNull(savedUser); }}Pour une isolation complète au niveau du test, utilisez ITestOutputHelper et créez le conteneur à l'intérieur de la méthode DisposeAsync :
public class IsolatedTest : IAsyncLifetime{ private PostgreSqlContainer _container;
public async Task InitializeAsync() { _container = new PostgreSqlBuilder().Build(); await _container.StartAsync(); }
public async Task DisposeAsync() { await _container.DisposeAsync(); }}3. Exécution parallèle des tests et gestion des ressources
L'un des principaux