Testes em C#: de Unit a E2E — como construir uma arquitetura de qualidade para verificações em .NET
A qualidade do software depende diretamente do sistema de testes. No ecossistema C# e .NET, existe uma pirâmide de testes bem estabelecida: testes unitários (unit), de integração (integration) e ponta a ponta (end-to-end). Neste artigo, vamos analisar cada nível com exemplos práticos, mostrar quais bibliotecas usar e fornecer modelos de código prontos.
1. Testes unitários: a base da estabilidade do código
Testes unitários verificam o funcionamento de métodos, classes ou componentes individuais isolados de dependências externas (banco de dados, API, sistema de arquivos). O objetivo principal é encontrar rapidamente regressões durante a refatoração.
Ferramentas para C#
- NUnit — framework clássico com atributos
[Test],[TestCase]. - xUnit — framework moderno dos criadores do NUnit, preferível em novos projetos.
- Moq — biblioteca para criar objetos mock.
- FluentAssertions — para asserts mais legíveis.
Exemplo de teste unitário com xUnit e Moq
Suponha que temos um serviço de cálculo de desconto que consulta um repositório de usuários:
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 desconto return amount; }}
// Testepublic 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); }}Dica: Use [Theory] e [InlineData] no xUnit para testes parametrizados — isso reduz a duplicação de código.
2. Testes de integração: verificando a conexão entre componentes
Testes de integração verificam como seus módulos interagem com sistemas externos reais: bancos de dados, filas, serviços web. Diferente dos testes unitários, aqui não usamos mocks — levantamos ambientes reais.
Bibliotecas e abordagens
- TestContainers — executa contêineres Docker (PostgreSQL, SQL Server, Redis) diretamente dos testes.
- WebApplicationFactory (do Microsoft.AspNetCore.Mvc.Testing) — cria um host de teste ASP.NET Core.
- Respawn — limpa o banco de dados entre os testes sem recriar o contêiner.
Exemplo de teste de integração com TestContainers e WebApplicationFactory
Vamos verificar se nossa API salva corretamente um usuário no 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 => { // Substituímos a string de conexão real pelo contêiner services.RemoveAll<DbContextOptions<AppDbContext>>(); se