Introdução: Por que testar não é um luxo, mas uma necessidade
No mundo do desenvolvimento moderno em C# (.NET), a qualidade do código determina não apenas a velocidade de lançamento do produto, mas também sua reputação. Erros em produção, falhas repentinas após atualizar uma dependência ou funcionamento incorreto da lógica de negócios são cenários que podem ser evitados com testes bem estruturados.
Muitos desenvolvedores, especialmente os iniciantes, veem os testes como "trabalho extra". No entanto, na prática, os testes economizam horas de depuração e refatoração. Neste artigo, vamos explorar os três níveis principais de teste no ecossistema C#: Unit (unitários), Integration (integração) e E2E (End-to-End). Você verá como cada um resolve seus próprios problemas, quais ferramentas usar e como escrever testes limpos e sustentáveis.
1. Testes Unitários: O Alicerce da Qualidade
Testes unitários verificam blocos de código isolados (geralmente um método ou classe) separados de dependências externas. Em C#, este é o nível de teste mais rápido e estável. O objetivo principal é garantir que cada função funcione corretamente com diferentes dados de entrada.
Ferramentas para Testes Unitários em C#
Os frameworks mais populares para .NET são xUnit (recomendado pela Microsoft) e NUnit. Para substituir dependências, usa-se a biblioteca Moq ou o FakeItEasy nativo do .NET.
Exemplo de Teste Unitário (xUnit + Moq)
Suponha que temos um serviço de cálculo de desconto que depende de um repositório de usuários:
public class DiscountService{ private readonly IUserRepository _userRepo;
public DiscountService(IUserRepository userRepo) { _userRepo = userRepo; }
public decimal CalculateDiscount(int userId, decimal orderTotal) { var user = _userRepo.GetById(userId); if (user.IsVip) return orderTotal * 0.2m; // 20% de desconto return orderTotal * 0.05m; // 5% de desconto }}Teste para este método:
public class DiscountServiceTests{ [Fact] public void CalculateDiscount_WhenUserIsVip_Returns20Percent() { // Arrange var mockRepo = new Mock<IUserRepository>(); mockRepo.Setup(repo => repo.GetById(1)) .Returns(new User { Id = 1, IsVip = true }); var service = new DiscountService(mockRepo.Object);
// Act var result = service.CalculateDiscount(1, 100m);
// Assert Assert.Equal(20m, result); }}Importante: Em testes unitários, não acessamos o banco de dados real. Usamos um mock para isolar a lógica do DiscountService.
2. Testes de Integração: Verificando a Conexão entre Componentes
Testes de integração verificam a interação de vários componentes reais: banco de dados, API, sistema de arquivos. No .NET, para isso, frequentemente usamos WebApplicationFactory (para ASP.NET Core) e contêineres de teste (Testcontainers).
Características dos Testes de Integração
- Mais lentos que testes unitários — exigem a inicialização de infraestrutura (BD, servidor HTTP).
- Mais caros para manter — é necessário gerenciar o estado do ambiente de teste.
- Valor — identificam problemas de configuração, mapeamento de dados e funcionamento com o BD real.
Exemplo de Teste de Integração (ASP.NET Core + xUnit)
Suponha que temos um controller para trabalhar com pedidos. O teste verificará se uma requisição à API real retorna o JSON correto:
public class OrdersApiTests : IClassFixture<WebApplicationFactory<Program>>{ private readonly HttpClient _client;
public OrdersApiTests(WebApplicationFactory<Program> factory) { _client = f