Testes automatizados em .NET: unitários, integração ou ambos?

Testes automatizados em .NET: unitários, integração ou ambos?
Publicado em 04/11/2026 por Admin

Testes automatizados são uma das práticas mais importantes para manter a qualidade de aplicações de software.

Entretanto, existe uma dúvida bastante comum entre desenvolvedores .NET: devemos investir em testes unitários, testes de integração ou implementar os dois?

Em muitos projetos, encontramos situações extremas.

Algumas equipes escrevem exclusivamente testes unitários, utilizando mocks para praticamente todas as dependências. Outras preferem testar tudo por meio de testes de integração, executando a aplicação com banco de dados e infraestrutura real.

Ambas as abordagens podem apresentar limitações.

Testes unitários oferecem velocidade e isolamento. Testes de integração verificam se diferentes componentes realmente funcionam juntos.

Neste artigo, vamos entender as diferenças entre essas estratégias, conhecer exemplos utilizando C# e xUnit e descobrir como estruturar uma suíte de testes eficiente para projetos .NET.

1. O que são testes automatizados?

Testes automatizados são verificações executadas por código para confirmar se determinada funcionalidade apresenta o comportamento esperado.

Em vez de depender exclusivamente de verificações manuais, criamos testes que podem ser executados repetidamente.

Isso permite identificar regressões, validar alterações e aumentar a confiança durante o desenvolvimento.

No ecossistema .NET, existem diferentes frameworks para trabalhar com testes:

  • xUnit.
  • NUnit.
  • MSTest.

Todos permitem implementar testes unitários e de integração.

Neste artigo, utilizaremos xUnit nos exemplos.

2. Testes unitários: validando pequenas unidades de código

O teste unitário tem como objetivo verificar o comportamento de uma unidade específica da aplicação, normalmente um método ou uma classe.

A principal característica é o isolamento.

Imagine uma classe responsável por calcular descontos de pedidos.

 
public class DiscountCalculator
{
    public decimal Calculate(decimal amount)
    {
        if (amount >= 1000)
            return amount * 0.10m;

        return amount * 0.05m;
    }
}
 

Podemos criar um teste unitário para verificar seu comportamento:

 
public class DiscountCalculatorTests
{
    [Fact]
    public void Calculate_WhenAmountIsGreaterThan1000_Returns10Percent()
    {
        var calculator = new DiscountCalculator();

        var result = calculator.Calculate(1500);

        Assert.Equal(150m, result);
    }
}
 

O teste verifica se um pedido de 1.500 recebe corretamente um desconto de 10%.

Observe que não precisamos de banco de dados, API externa ou infraestrutura adicional.

O teste é rápido, simples e independente.

Quando utilizar testes unitários?

São especialmente indicados para:

  • Regras de negócio.
  • Cálculos.
  • Validações.
  • Conversões de dados.
  • Métodos de transformação.
  • Processamento de informações.
  • Comportamentos condicionais.

Quanto mais complexas forem as regras de negócio, maior tende a ser o benefício de possuir testes unitários bem estruturados.

3. Utilizando mocks para isolar dependências

Em aplicações reais, nossas classes normalmente dependem de outros componentes.

Imagine um serviço responsável por criar pedidos e salvar as informações em um repositório.

 
public interface IOrderRepository
{
    Task SaveAsync(Order order);
}
 

O serviço:

 
public class OrderService
{
    private readonly IOrderRepository _repository;

    public OrderService(IOrderRepository repository)
    {
        _repository = repository;
    }

    public async Task CreateAsync(Order order)
    {
        if (order.Amount <= 0)
            throw new ArgumentException("Invalid amount");

        await _repository.SaveAsync(order);
    }
}
 

Para testar essa classe sem depender de um banco de dados, podemos utilizar um mock.

Com a biblioteca Moq:

 
[Fact]
public async Task CreateAsync_WithValidOrder_SavesOrder()
{
    var repository = new Mock<IOrderRepository>();

    var service = new OrderService(repository.Object);

    var order = new Order
    {
        Amount = 500
    };

    await service.CreateAsync(order);

    repository.Verify(
        x => x.SaveAsync(order),
        Times.Once);
}
 

Nesse cenário, estamos verificando se o serviço chama corretamente o repositório.

Não estamos testando se o banco de dados realmente salva o pedido.

Essa distinção é fundamental.

Um teste unitário pode confirmar que determinada interação aconteceu, mas não garante que toda a infraestrutura esteja funcionando corretamente.

4. Testes de integração: verificando componentes trabalhando juntos

Enquanto os testes unitários verificam componentes isoladamente, os testes de integração avaliam se diferentes partes da aplicação funcionam corretamente quando conectadas.

Imagine uma API ASP.NET Core que recebe uma requisição, valida os dados, executa uma regra de negócio e salva informações no SQL Server.

Um teste de integração pode verificar esse fluxo.

Nesse cenário, podemos validar:

  • Configuração da aplicação.
  • Injeção de dependência.
  • Pipeline HTTP.
  • Middlewares.
  • Controllers ou Minimal APIs.
  • Integração com banco de dados.
  • Serialização e desserialização.
  • Comportamento das respostas HTTP.

O ASP.NET Core disponibiliza a classe WebApplicationFactory, que facilita a criação de um ambiente de testes para a aplicação.

Para utilizá-la, normalmente precisamos do pacote:

 
dotnet add package Microsoft.AspNetCore.Mvc.Testing
 

Um exemplo simplificado:

 
public class OrdersApiTests :
    IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public OrdersApiTests(
        WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task GetOrders_ReturnsSuccessStatusCode()
    {
        var response = await _client.GetAsync("/api/orders");

        response.EnsureSuccessStatusCode();
    }
}
 

Esse exemplo utiliza o servidor de testes do ASP.NET Core para executar uma requisição HTTP contra a aplicação.

Para testar cenários mais próximos da produção, podemos substituir configurações e dependências, utilizar um banco de dados de testes e preparar dados específicos.

5. Testes unitários versus testes de integração

As duas abordagens possuem objetivos diferentes.

Característica Testes unitários Testes de integração
Objetivo Validar unidades isoladas Validar componentes integrados
Velocidade Geralmente muito rápidos Geralmente mais lentos
Banco de dados Normalmente não utiliza Pode utilizar
Dependências Frequentemente simuladas Preferencialmente reais no escopo testado
Complexidade Menor Maior
Diagnóstico Mais localizado Pode envolver vários componentes
Uso ideal Regras de negócio Infraestrutura e fluxos integrados

Um detalhe importante: testes de integração não precisam obrigatoriamente utilizar todos os serviços reais da produção.

É perfeitamente possível substituir serviços externos por implementações controladas, desde que o objetivo do teste seja claramente definido.

6. Afinal, devemos utilizar os dois?

Sim. Para a maioria das aplicações profissionais, uma combinação equilibrada costuma ser a melhor estratégia.

Imagine uma aplicação de processamento de pagamentos.

Podemos organizar os testes da seguinte maneira:

Testes unitários

  • Validação dos dados do pagamento.
  • Cálculo de taxas.
  • Regras de autorização.
  • Regras de negócio.
  • Tratamento de situações específicas.

Testes de integração

  • Comunicação com o banco de dados.
  • Persistência de pagamentos.
  • Configuração de repositórios.
  • Comunicação entre componentes.
  • Funcionamento dos endpoints.
  • Integração com serviços internos.

Dessa maneira, evitamos utilizar testes de integração para verificar comportamentos que poderiam ser validados de maneira muito mais simples por testes unitários.

A documentação oficial do ASP.NET Core também recomenda limitar os testes de integração aos cenários de infraestrutura mais importantes, utilizando testes unitários quando ambos poderiam validar o mesmo comportamento.

7. Como organizar os projetos de testes em uma solução .NET?

Uma boa organização facilita a manutenção e permite executar diferentes categorias de testes separadamente.

Uma estrutura possível:

 
MyApplication
│
├── src
│   ├── MyApplication.Api
│   ├── MyApplication.Application
│   ├── MyApplication.Domain
│   └── MyApplication.Infrastructure
│
└── tests
    ├── MyApplication.UnitTests
    └── MyApplication.IntegrationTests
 

O projeto UnitTests concentra os testes de regras de negócio, serviços e componentes isolados.

O projeto IntegrationTests concentra os testes que dependem da integração entre diferentes partes da solução.

Essa separação também permite executar apenas os testes unitários durante ciclos rápidos de desenvolvimento.

8. Automatizando a execução dos testes

Testes automatizados precisam fazer parte do fluxo de desenvolvimento.

Uma das maneiras mais simples de executar os testes é utilizar o próprio CLI do .NET:

 
dotnet test
 

Também podemos executar um projeto específico:

 
dotnet test tests/MyApplication.UnitTests
 

Em ambientes de CI/CD, podemos incluir a execução dos testes antes de permitir que alterações sejam integradas à branch principal.

Um pipeline básico pode seguir esta sequência:

  1. Restaurar dependências.
  2. Compilar a solução.
  3. Executar testes unitários.
  4. Executar testes de integração.
  5. Gerar relatórios.
  6. Publicar os resultados.

Isso ajuda a identificar problemas antes que o código chegue aos ambientes de homologação ou produção.

9. Erros comuns ao implementar testes automatizados

Existem algumas práticas que podem comprometer a qualidade da suíte de testes.

Criar testes excessivamente dependentes de mocks

Quando praticamente tudo é simulado, podemos acabar testando apenas as interações entre mocks, sem validar comportamentos realmente importantes.

Testar detalhes internos em vez de comportamentos

Um teste deve verificar o resultado esperado de uma funcionalidade, e não depender desnecessariamente de detalhes de implementação.

Criar testes dependentes da ordem de execução

Cada teste deve ser suficientemente independente para ser executado isoladamente.

Não testar cenários negativos

Testar apenas o caminho de sucesso deixa muitas possibilidades de falha sem validação.

Ignorar testes de integração

Uma aplicação pode possuir centenas de testes unitários e ainda apresentar problemas na comunicação com o banco de dados ou na configuração do pipeline HTTP.

Conclusão

Testes unitários e testes de integração não são estratégias concorrentes.

Eles resolvem problemas diferentes e se complementam.

Os testes unitários oferecem velocidade, isolamento e facilidade de diagnóstico. Os testes de integração aumentam a confiança de que os componentes realmente funcionam juntos.

Para aplicações .NET profissionais, a recomendação é começar pelas regras de negócio, construir uma boa cobertura unitária e adicionar testes de integração para validar os principais fluxos e pontos de infraestrutura.

O objetivo não é possuir a maior quantidade possível de testes, mas construir uma suíte que realmente ajude a prevenir problemas e permita evoluir a aplicação com segurança.