Testes de Integração com C# e Docker: Como Eliminar Problemas de Ambiente
Todo desenvolvedor C# já passou por esta situação: os testes passam na máquina local, mas falham no servidor de CI. O motivo geralmente são diferenças no ambiente: versão do banco de dados, configurações do Redis, serviços de teste ausentes. Testes de integração com C# e Docker resolvem este problema de uma vez por todas. Neste artigo, veremos como configurar um ambiente de teste isolado usando contêineres, escrever testes de integração reais e integrá-los a um pipeline de CI/CD.
Por que o Docker é a Ferramenta Perfeita para Testes de Integração C#
Testes de integração verificam a interação entre componentes do sistema: aplicação + banco de dados, API + cache, microsserviços entre si. Sem o Docker, você precisaria:
- Instalar PostgreSQL, SQL Server, Redis em cada máquina do desenvolvedor
- Configurar o servidor CI com as mesmas versões
- Limpar dados entre execuções de teste
O Docker resolve todas essas tarefas. Você descreve a infraestrutura em código (Dockerfile, docker-compose.yml), e ela é implantada de forma idêntica em qualquer sistema. Para C# (.NET), isso é especialmente relevante, já que a Microsoft oferece suporte ativo à conteinerização: imagens oficiais do .NET SDK, SQL Server em um contêiner Linux, integração com Azure DevOps.
Configuração do Projeto: Testcontainers + xUnit
Para testes de integração com C# e Docker, usaremos a biblioteca Testcontainers (disponível no NuGet). Ela gerencia automaticamente o ciclo de vida do contêiner: inicia antes dos testes, para após a conclusão.
Passo 1. Instalando Pacotes
Crie um novo projeto para testes ou abra um existente. Adicione os pacotes NuGet:
dotnet add package Testcontainersdotnet add package Testcontainers.PostgreSqldotnet add package xunitdotnet add package FluentAssertionsPasso 2. Classe Base para Testes com Contêineres
Vamos criar uma classe abstrata que iniciará o PostgreSQL antes de executar os testes:
using Testcontainers.PostgreSql;using Xunit;
public abstract class IntegrationTestBase : IAsyncLifetime{ private readonly PostgreSqlContainer _postgresContainer;
protected string ConnectionString => _postgresContainer.GetConnectionString();
protected IntegrationTestBase() { _postgresContainer = new PostgreSqlBuilder() .WithImage("postgres:15-alpine") .WithDatabase("testdb") .WithUsername("test") .WithPassword("test123") .Build(); }
public async Task InitializeAsync() { await _postgresContainer.StartAsync(); }
public async Task DisposeAsync() { await _postgresContainer.StopAsync(); }}A classe implementa IAsyncLifetime do xUnit — o contêiner será iniciado antes do primeiro teste e parado após o último. O método GetConnectionString() retorna a porta dinâmica que o Docker atribuiu ao contêiner.
Passo 3. Escrevendo um Teste de Integração
Agora vamos escrever um teste que verifica o funcionamento do repositório com um banco de dados real:
public class UserRepositoryTests : IntegrationTestBase{ [Fact] public async Task CreateUser_Should_SaveToDatabase() { // Arrange await using var connection = new NpgsqlConnection(ConnectionString); await connection.OpenAsync(); var repository = new UserRepository(connection); var user = new User { Name = "Ivan", Email = "ivan@example.com" };
// Act var userId = await repository.CreateAsync(user);
// Assert userId.Should().BeGreaterThan(0); var savedUser = await repository.GetByIdAsync(userId); savedUser.Should().NotBeNull(); savedUser.Name.Sho