Partilhar via


Testando ASP.NET serviços principais e aplicativos Web

Gorjeta

Este conteúdo é um trecho do eBook, .NET Microservices Architecture for Containerized .NET Applications, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

Miniatura da capa do eBook .NET Microservices Architecture for Containerized .NET Applications.

Os controladores são uma parte central de qualquer serviço de API principal ASP.NET e ASP.NET aplicativo Web MVC. Como tal, deve ter confiança de que se comportam como pretendido para a sua aplicação. Os testes automatizados podem fornecer essa confiança e detetar erros antes que eles cheguem à produção.

Você precisa testar como o controlador se comporta com base em entradas válidas ou inválidas e testar as respostas do controlador com base no resultado da operação de negócios que ele executa. No entanto, você deve ter estes tipos de testes para seus microsserviços:

  • Testes unitários. Esses testes garantem que os componentes individuais do aplicativo funcionem conforme o esperado. As asserções testam a API do componente.

  • Testes de integração. Esses testes garantem que as interações de componentes funcionem conforme o esperado em relação a artefatos externos, como bancos de dados. As asserções podem testar a API do componente, a interface do usuário ou os efeitos colaterais de ações como E/S do banco de dados, registro em log, etc.

  • Testes funcionais para cada microsserviço. Esses testes garantem que o aplicativo funcione como esperado da perspetiva do usuário.

  • Testes de serviço. Esses testes garantem que os casos de uso de serviço de ponta a ponta, incluindo o teste de vários serviços ao mesmo tempo, sejam testados. Para esse tipo de teste, você precisa preparar o ambiente primeiro. Nesse caso, significa iniciar os serviços (por exemplo, usando docker-compose up).

Implementando testes de unidade para APIs Web ASP.NET Core

O teste de unidade envolve o teste de uma parte de um aplicativo isoladamente de sua infraestrutura e dependências. Quando você testa a lógica do controlador de teste de unidade, apenas o conteúdo de uma única ação ou método é testado, não o comportamento de suas dependências ou da estrutura em si. Os testes de unidade não detetam problemas na interação entre os componentes — esse é o objetivo dos testes de integração.

Ao testar as ações do controlador, certifique-se de se concentrar apenas no comportamento delas. Um teste de unidade de controlador evita coisas como filtros, roteamento ou vinculação de modelo (o mapeamento de dados de solicitação para um ViewModel ou DTO). Como eles se concentram em testar apenas uma coisa, os testes de unidade geralmente são simples de escrever e rápidos de executar. Um conjunto bem escrito de testes de unidade pode ser executado com freqüência sem muita sobrecarga.

Os testes de unidade são implementados com base em estruturas de teste como xUnit.net, MSTest, Moq ou NUnit. Para o aplicativo de exemplo eShopOnContainers, estamos usando xUnit.

Quando você escreve um teste de unidade para um controlador de API da Web, você instancia a classe do controlador diretamente usando a nova palavra-chave em C#, para que o teste seja executado o mais rápido possível. O exemplo a seguir mostra como fazer isso ao usar xUnit como a estrutura de teste.

[Fact]
public async Task Get_order_detail_success()
{
    //Arrange
    var fakeOrderId = "12";
    var fakeOrder = GetFakeOrder();

    //...

    //Act
    var orderController = new OrderController(
        _orderServiceMock.Object,
        _basketServiceMock.Object,
        _identityParserMock.Object);

    orderController.ControllerContext.HttpContext = _contextMock.Object;
    var actionResult = await orderController.Detail(fakeOrderId);

    //Assert
    var viewResult = Assert.IsType<ViewResult>(actionResult);
    Assert.IsAssignableFrom<Order>(viewResult.ViewData.Model);
}

Implementação de testes funcionais e de integração para cada microsserviço

Como observado, os testes de integração e os testes funcionais têm finalidades e objetivos diferentes. No entanto, a maneira como você implementa ambos ao testar controladores ASP.NET Core é semelhante, portanto, nesta seção, nos concentramos nos testes de integração.

O teste de integração garante que os componentes de um aplicativo funcionem corretamente quando montados. O ASP.NET Core suporta testes de integração usando estruturas de teste de unidade e um host de teste interno que pode ser usado para lidar com solicitações sem sobrecarga de rede.

Ao contrário do teste de unidade, os testes de integração frequentemente envolvem preocupações com a infraestrutura do aplicativo, como um banco de dados, sistema de arquivos, recursos de rede ou solicitações e respostas da Web. Os testes de unidade usam falsificações ou objetos simulados no lugar dessas preocupações. Mas o objetivo dos testes de integração é confirmar que o sistema funciona como esperado com esses sistemas, portanto, para testes de integração, você não usa falsificações ou objetos simulados. Em vez disso, você inclui a infraestrutura, como acesso ao banco de dados ou invocação de serviço de outros serviços.

Como os testes de integração exercem segmentos de código maiores do que os testes de unidade, e como os testes de integração dependem de elementos de infraestrutura, eles tendem a ser ordens de magnitude mais lentos do que os testes de unidade. Assim, é uma boa ideia limitar quantos testes de integração você escreve e executa.

ASP.NET Core inclui um host de teste interno que pode ser usado para lidar com solicitações HTTP sem sobrecarga de rede, o que significa que você pode executar esses testes mais rápido do que ao usar um host real. O host da Web de teste (TestServer) está disponível em um componente NuGet como Microsoft.AspNetCore.TestHost. Ele pode ser adicionado a projetos de teste de integração e usado para hospedar aplicativos ASP.NET Core.

Como você pode ver no código a seguir, quando você cria testes de integração para controladores ASP.NET Core, você instancia os controladores através do host de teste. Essa funcionalidade é comparável a uma solicitação HTTP, mas é executada mais rapidamente.

public class PrimeWebDefaultRequestShould
{
    private readonly TestServer _server;
    private readonly HttpClient _client;

    public PrimeWebDefaultRequestShould()
    {
        // Arrange
        _server = new TestServer(new WebHostBuilder()
           .UseStartup<Startup>());
        _client = _server.CreateClient();
    }

    [Fact]
    public async Task ReturnHelloWorld()
    {
        // Act
        var response = await _client.GetAsync("/");
        response.EnsureSuccessStatusCode();
        var responseString = await response.Content.ReadAsStringAsync();
        // Assert
        Assert.Equal("Hello World!", responseString);
    }
}

Recursos adicionais

Implementando testes de serviço em um aplicativo de vários contêineres

Como observado anteriormente, quando você testa aplicativos de vários contêineres, todos os microsserviços precisam estar em execução no host do Docker ou no cluster de contêineres. Os testes de serviço de ponta a ponta que incluem várias operações envolvendo vários microsserviços exigem que você implante e inicie todo o aplicativo no host do Docker executando docker-compose up (ou um mecanismo comparável se estiver usando um orquestrador). Quando todo o aplicativo e todos os seus serviços estiverem em execução, você poderá executar testes funcionais e de integração de ponta a ponta.

Existem algumas abordagens que você pode usar. No arquivo docker-compose.yml que você usa para implantar o aplicativo no nível da solução, você pode expandir o ponto de entrada para usar o teste dotnet. Você também pode usar outro arquivo de composição que executaria seus testes na imagem que você está segmentando. Usando outro arquivo de composição para testes de integração que inclui seus microsserviços e bancos de dados em contêineres, você pode garantir que os dados relacionados sejam sempre redefinidos para seu estado original antes de executar os testes.

Quando o aplicativo de composição estiver instalado e em execução, você poderá aproveitar os pontos de interrupção e exceções se estiver executando o Visual Studio. Ou você pode executar os testes de integração automaticamente em seu pipeline de CI nos Serviços de DevOps do Azure ou em qualquer outro sistema de CI/CD que ofereça suporte a contêineres do Docker.

Testes em eShopOnContainers

Os testes da aplicação de referência (eShopOnContainers) foram recentemente reestruturados e agora existem quatro categorias:

  1. Testes de unidade, simplesmente antigos testes de unidade regulares, contidos no {MicroserviceName}. Projetos UnitTests

  2. Testes funcionais/de integração de microsserviços, com casos de teste envolvendo a infraestrutura de cada microsserviço, mas isolados dos outros e contidos no {MicroserviceName}. Projetos de Testes Funcionais.

  3. Testes de funcionalidade/integração de aplicações, que se concentram na integração de microsserviços, com casos de teste que exercem vários microsserviços. Esses testes estão localizados no projeto Application.FunctionalTests.

Enquanto os testes de unidade e integração são organizados em uma pasta de teste dentro do projeto de microsserviço, os testes de aplicativo e carga são gerenciados separadamente na pasta raiz, como mostra a Figura 6-25.

Captura de tela do VS apontando alguns dos projetos de teste na solução.

Figura 6-25. Testar a estrutura de pastas no eShopOnContainers

Os testes de funcionalidade/integração de microsserviços e aplicativos são executados a partir do Visual Studio, usando o executor de testes regular, mas primeiro você precisa iniciar os serviços de infraestrutura necessários, com um conjunto de arquivos docker-compose contidos na pasta de teste da solução:

docker-compose-test.yml

version: '3.4'

services:
  redis.data:
    image: redis:alpine
  rabbitmq:
    image: rabbitmq:3-management-alpine
  sqldata:
    image: mcr.microsoft.com/mssql/server:2017-latest
  nosqldata:
    image: mongo

docker-compose-test.override.yml

version: '3.4'

services:
  redis.data:
    ports:
      - "6379:6379"
  rabbitmq:
    ports:
      - "15672:15672"
      - "5672:5672"
  sqldata:
    environment:
      - SA_PASSWORD=[PLACEHOLDER]
      - ACCEPT_EULA=Y
    ports:
      - "5433:1433"
  nosqldata:
    ports:
      - "27017:27017"

Importante

A Microsoft recomenda que você use o fluxo de autenticação mais seguro disponível. Se você estiver se conectando ao SQL do Azure, as Identidades Gerenciadas para recursos do Azure serão o método de autenticação recomendado.

Portanto, para executar os testes funcionais/de integração, você deve primeiro executar este comando, a partir da pasta de teste da solução:

docker-compose -f docker-compose-test.yml -f docker-compose-test.override.yml up

Como você pode ver, esses arquivos docker-compose apenas iniciam os microsserviços Redis, RabbitMQ, SQL Server e MongoDB.

Recursos adicionais