Testes de Integração C# com Docker: Nível Avançado
Testes de integração são uma etapa criticamente importante no desenvolvimento em C#, permitindo verificar a interação dos componentes do sistema com dependências reais: bancos de dados, filas de mensagens, APIs externas. O uso do Docker para esse fim tornou-se um padrão da indústria, mas em nível avançado surgem questões de isolamento, desempenho e execução paralela. Neste artigo, abordaremos técnicas profissionais de testes de integração C# com Docker, utilizando a biblioteca Testcontainers e configurações avançadas.
1. Configuração do Testcontainers para .NET: Infraestrutura Básica
A biblioteca Testcontainers para .NET permite gerenciar programaticamente contêineres Docker no ambiente de teste. Diferentemente da execução manual do docker-compose, o Testcontainers inicia e para automaticamente os contêineres para cada classe de teste ou módulo, garantindo um ambiente limpo.
Instale o pacote 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.0Exemplo de classe base para testes de integração com 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(); }}Agora, cada classe de teste pode usar essa fixture para obter uma instância isolada do banco de dados.
2. Isolamento de Testes e Gerenciamento de Estado dos Contêineres
O nível avançado exige controle refinado sobre o isolamento. Em vez de um contêiner para todo o conjunto de testes, use um contêiner por classe de teste ou até mesmo por teste. Isso evita o estado "sujo" do banco de dados entre os testes.
Implementação usando o atributo Collection no 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); }}Para isolamento total no nível do teste, use ITestOutputHelper e crie o contêiner dentro do método 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. Execução Paralela de Testes e Gerenciamento de Recursos
Um dos principais