Introdução: Por que os testes são necessários em C#?
\nNo desenvolvimento moderno com C# (.NET), a qualidade do código não é um luxo, mas uma necessidade. Os testes não apenas ajudam a capturar bugs antes de irem para produção, mas também servem como documentação viva e aceleram a refatoração. Existem três níveis principais de testes que formam a "pirâmide de testes": Unit (unitários), Integration (integração) e E2E (ponta a ponta). Neste artigo, abordaremos cada um deles com exemplos de código real em C#.
\n\n1. Testes Unitários: A Base da Qualidade
\nTestes unitários verificam um fragmento isolado de código (método, classe) sem dependências externas (banco de dados, sistema de arquivos, API). No ecossistema .NET, três frameworks são populares: xUnit (recomendado para novos projetos), NUnit e MSTest.
\n\nPrincípios Fundamentais (FIRST)
\n- \n
- Fast — rápidos (milissegundos). \n
- Isolated — isolados (usamos mocks). \n
- Repeatable — reproduzíveis. \n
- Self-validating — com autoverificação (Assert). \n
- Timely — oportunos. \n
Exemplo de Teste Unitário com xUnit e Moq
\nSuponha que temos um serviço para calcular desconto:
\npublic interface IDiscountService\n{\n decimal CalculateDiscount(decimal price, bool isLoyaltyMember);\n}\n\npublic class DiscountService : IDiscountService\n{\n public decimal CalculateDiscount(decimal price, bool isLoyaltyMember)\n {\n if (isLoyaltyMember) return price * 0.9m; // 10% de desconto\n return price;\n }\n}\nTeste usando Moq (para simular dependências) e xUnit:
\nusing Xunit;\nusing Moq;\n\npublic class DiscountServiceTests\n{\n [Fact]\n public void CalculateDiscount_WhenLoyaltyMember_ReturnsDiscountedPrice()\n {\n // Arrange\n var service = new DiscountService();\n var price = 100m;\n\n // Act\n var result = service.CalculateDiscount(price, true);\n\n // Assert\n Assert.Equal(90m, result);\n }\n\n [Theory]\n [InlineData(100, false, 100)]\n [InlineData(200, true, 180)]\n public void CalculateDiscount_MultipleCases_ReturnsCorrectValues(decimal price, bool isLoyalty, decimal expected)\n {\n var service = new DiscountService();\n var result = service.CalculateDiscount(price, isLoyalty);\n Assert.Equal(expected, result);\n }\n}\nDica: Use o atributo [Fact] para testes únicos e [Theory] + [InlineData] para testes parametrizados.
2. Testes de Integração: Verificando a Conexão entre Componentes
\nTestes de integração verificam como vários módulos funcionam juntos — por exemplo, um controller, um serviço e um banco de dados real (ou seu substituto, como SQLite in-memory). No ASP.NET Core, usamos WebApplicationFactory para isso.
Configuração do WebApplicationFactory
\nCriaremos uma classe de teste que sobe um host de teste web:
\nusing Microsoft.AspNetCore.Mvc.Testing;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing MyApp.Data;\n\npublic class CustomWebApplicationFactory : WebApplicationFactory<Program>\n{\n protected override void ConfigureWebHost(IWebHostBuilder builder)\n {\n builder.ConfigureServices(services =>\n {\n // Remove o DbContext real\n var descriptor = services.SingleOrDefault(\n d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));\n if (descriptor != null) services.Remove(descriptor);\n\n // Adiciona banco in-memory\n services.AddDbContext<AppDbContext>(options =>\n