Partilhar via


Testes de unidade e integração em aplicativos de APIs mínimas

Por Fiyaz Bin Hasan e Rick Anderson

Introdução aos testes de integração

Os testes de integração avaliam os componentes de um aplicativo em um nível mais amplo do que os testes de unidade. Os testes de unidade são usados para testar componentes de software isolados, como métodos de classe individuais. Os testes de integração confirmam que dois ou mais componentes de aplicativo trabalham juntos para produzir um resultado esperado, possivelmente incluindo todos os componentes necessários para processar totalmente uma solicitação.

Esses testes mais amplos são usados para testar a infraestrutura do aplicativo e toda a estrutura, geralmente incluindo os seguintes componentes:

  • Banco de dados
  • Sistema de arquivos
  • Dispositivos de rede
  • Pipeline de solicitação-resposta

Os testes de unidade usam componentes fabricados, conhecidos como fakes ou objetos fictícios, no lugar de componentes de infraestrutura.

Ao contrário dos testes de unidade, os testes de integração:

  • Usam os componentes reais que o aplicativo usa em produção.
  • Exigem mais código e processamento de dados.
  • Demoraram mais para serem executados.

Portanto, limite o uso de testes de integração aos cenários de infraestrutura mais importantes. Se um comportamento puder ser testado usando um teste de unidade ou um teste de integração, escolha o teste de unidade.

Em discussões sobre testes de integração, o projeto testado é frequentemente chamado de System Under Test ou "SUT" para abreviar. "SUT" é usado ao longo deste artigo para se referir ao aplicativo ASP.NET Core que está sendo testado.

Não escreva testes de integração para cada permutação de dados e acesso a arquivos com bancos de dados e sistemas de arquivos. Independentemente de quantos lugares em um aplicativo interagem com bancos de dados e sistemas de arquivos, um conjunto focado de testes de integração de leitura, gravação, atualização e exclusão geralmente é capaz de testar adequadamente os componentes do banco de dados e do sistema de arquivos. Use testes de unidade para testes de rotina da lógica do método que interage com esses componentes. Em testes de unidade, o uso de infraestruturas falsas ou fictícias resulta em uma execução de teste mais rápida.

Testes de integração de ASP.NET Core

Os testes de integração no ASP.NET Core exigem o seguinte:

  • Um projeto de teste é usado para conter e executar os testes. O projeto de teste tem uma referência ao SUT.
  • O projeto de teste cria um host Web de teste para o SUT e usa um cliente do servidor de teste para lidar com solicitações e respostas com o SUT.
  • Um executor de teste é usado para executar os testes e relatar os resultados.

Os testes de integração seguem uma sequência de eventos que incluem as etapas de teste Organizar, Atuar e Afirmar:

  1. O host da Web do SUT está configurado.
  2. Um cliente do servidor de teste é criado para enviar solicitações ao aplicativo.
  3. A etapa de teste Organizar é executada: o aplicativo de teste prepara uma solicitação.
  4. A etapa de teste Atuar é executada: o cliente envia a solicitação e recebe a resposta.
  5. A etapa de teste Afirmar é executada: a resposta real é validada como uma aprovada ou reprovada com base em uma resposta esperada.
  6. O processo continua até que todos os testes sejam executados.
  7. Os resultados do teste são reportados.

Normalmente, o host da Web de teste é configurado de forma diferente do host da Web normal do aplicativo para as execuções de teste. Por exemplo, um banco de dados diferente ou configurações de aplicativo diferentes podem ser usadas para os testes.

Os componentes de infraestrutura, como o host da Web de teste e o servidor de teste na memória (TestServer), são fornecidos ou gerenciados pelo pacote Microsoft.AspNetCore.Mvc.Testing. O uso desse pacote simplifica a criação e a execução do teste.

O pacote Microsoft.AspNetCore.Mvc.Testing manipula as seguintes tarefas:

  • Copia o arquivo de dependências (.deps) do SUT para o diretório bin do projeto de teste.
  • Define a raiz de conteúdo para a raiz do projeto do SUT para que arquivos estáticos e páginas/exibições sejam encontradas quando os testes forem executados.
  • Fornece a classe WebApplicationFactory para simplificar a inicialização do aplicativo testado com TestServer.

A documentação de testes de unidade descreve como configurar um projeto de teste e um executor de teste, juntamente com instruções detalhadas sobre como executar testes e recomendações sobre como nomear testes e classes de teste.

Separe os testes de unidade dos testes de integração em projetos diferentes. Separando os testes:

  • Ajuda a garantir que os componentes de teste de infraestrutura não sejam incluídos acidentalmente nos testes de unidade.
  • Permite o controle sobre quais conjuntos de testes são executados.

O código de exemplo no GitHub fornece um exemplo de testes de unidade e integração em um aplicativo de APIs mínimas.

Tipos de implementação IResult

Os tipos de implementação IResult no namespace Microsoft.AspNetCore.Http.HttpResults podem ser usados para testar manipuladores de rota mínimos de unidade ao usar métodos nomeados em vez de lambdas.

O código abaixo usa a classe NotFound<TValue>:

[Fact]
public async Task GetTodoReturnsNotFoundIfNotExists()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var notFoundResult = (NotFound) result.Result;

    Assert.NotNull(notFoundResult);
}

O código abaixo usa a classe Ok<TValue>:

[Fact]
public async Task GetTodoReturnsTodoFromDatabase()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    context.Todos.Add(new Todo
    {
        Id = 1,
        Title = "Test title",
        Description = "Test description",
        IsDone = false
    });

    await context.SaveChangesAsync();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var okResult = (Ok<Todo>)result.Result;

    Assert.NotNull(okResult.Value);
    Assert.Equal(1, okResult.Value.Id);
}

Recursos adicionais