Тестирование служб и веб-приложений ASP.NET Core

Совет

Это содержимое является фрагментом из электронной книги, архитектуры микрослужб .NET для контейнерных приложений .NET, доступных в документации .NET или в виде бесплатного скачиваемого PDF-файла, который можно читать в автономном режиме.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Контроллеры — это центральный элемент любой службы ASP.NET Core API или веб-приложения ASP.NET MVC. Поэтому необходимо убедиться в том, что они работают так, как это требуется вашему приложению. Это можно сделать с помощью автоматических тестов, которые помогут также обнаружить имеющиеся ошибки до ввода контроллеров в рабочую среду.

Необходимо протестировать работу контроллера при допустимых и недопустимых значениях входных данных, а также проверить реакцию контроллера в сопоставлении с результатами бизнес-операции, которую он выполняет. Вместе с тем вам необходимо выполнить следующие тесты микрослужб.

  • Модульные тесты. Эти тесты позволяют проверить правильность работы отдельных компонентов приложения. С помощью утверждений тестируется API компонента.

  • Интеграционные тесты. Такие тесты позволяют проверить правильность взаимодействия компонента с внешними устройствами, например с базами данных. С помощью утверждений можно тестировать API компонента, пользовательский интерфейс и побочные эффекты от таких операций, как операции ввода-вывода в базах данных, ведение журнала и т. д.

  • Функциональные тесты для каждой микрослужбы. Позволяют проверить правильность работы приложения с точки зрения пользователя.

  • Тесты служб. Позволяют проверить, были ли протестированы варианты сквозного использования служб, включая одновременное тестирование нескольких служб. Для такого тестирования необходимо сначала подготовить среду. В данном случае это означает запуск служб (например, с помощью docker-compose up).

Реализация модульных тестов для веб-API ASP.NET Core

Модульное тестирование — это тестирование части приложения изолированно от его инфраструктуры и зависимостей. При модульном тестировании логики контроллера проверяется только содержимое отдельного действия или метода, а работа его зависимостей и самой платформы в целом не проверяется. Модульные тесты не выявляют проблемы с взаимодействием между компонентами. Для этого предназначены интеграционные тесты.

Выполняя модульное тестирование действий контроллера, следует сосредоточиться только на этих действиях. Модульное тестирование контроллера не учитывает такие аспекты, как фильтры, маршрутизация и привязка модели (сопоставление данных запроса с ViewModel или DTO). Благодаря их узкой направленности модульные тесты, как правило, проще создавать, а выполняются они быстрее. Правильно составленный набор модульных тестов можно выполнять часто без значительных затрат.

Модульные тесты реализуются на основе платформ тестирования, таких как xUnit.net MSTest, Moq и NUnit. Для примера приложения eShopOnContainers мы используем xUnit.

При написании модульного теста для контроллера веб-API создается экземпляр класса контроллера непосредственно с помощью нового ключевое слово в C#, чтобы тест выполнялся как можно быстрее. В следующем примере показано, как это можно сделать в случае использования платформы тестирования xUnit.

[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);
}

Реализация интеграционных и функциональных тестов для каждой микрослужбы

Как уже отмечалось, интеграционные и функциональные тесты имеют разное назначение. Однако способы реализации тех и других при тестировании контроллеров ASP.NET Core похожи. Поэтому в этом разделе мы сосредоточимся на интеграционных тестах.

При интеграционном тестировании проверяется, будут ли компоненты приложения правильно работать после сборки. Платформа ASP.NET Core поддерживает интеграционное тестирование с использованием платформ модульного тестирования и встроенного веб-сервера тестирования, который может использоваться для обработки запросов без нагрузки на сеть.

В отличие от модульного тестирования, при интеграционном тестировании часто затрагиваются различные аспекты инфраструктуры приложения, например базы данных, файловые системы, сетевые ресурсы, веб-запросы и ответы. При модульном тестировании используются имитации или макеты объектов вместо реальных объектов. Интеграционное тестирование предназначено для проверки того, что система работает должным образом с реальными объектами, поэтому при интеграционном тестировании не используются имитации. В процесс тестирования вовлекается инфраструктура — проверяется, например, выполнение запросов на доступ к базам данных или вызов одной службы другой службой.

Так как при интеграционном тестировании отрабатывается значительно больший, чем при модульном тестировании, объем кода, а также из-за того, что при этом затрагивается инфраструктура элементов, такие тесты обычно выполняются на порядки медленнее, чем модульные тесты. Поэтому рекомендуется ограничить количество интеграционных тестов.

Платформа ASP.NET Core содержит встроенный веб-сервер тестирования, который можно использовать для обработки HTTP-запросов без нагрузки на сеть. Это позволяет выполнять тесты быстрее, чем при работе с реальными веб-серверами. Веб-сервер тестирования (TestServer) доступен в компоненте NuGet как Microsoft.AspNetCore.TestHost. Его можно добавлять в проекты интеграционного тестирования и использовать для размещения приложений ASP.NET Core.

Как можно видеть в следующем коде, при создании интеграционных тестов для контроллеров ASP.NET Core создается экземпляр контроллера на сервере тестирования. Это похоже на HTTP-запрос, но выполняется быстрее.

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);
    }
}

Дополнительные ресурсы

Реализация тестов служб в приложении с несколькими контейнерами

Как уже упоминалось, при тестировании приложения с несколькими контейнерами необходимо, чтобы все микрослужбы были запущены на узле Docker или в кластере контейнера. Для выполнения сквозного тестирования служб, включающего в себя множество операций, затрагивающих несколько микрослужб, требуется развертывание и запуск всего приложения на узле Docker с помощью docker-compose up (или аналогичного механизма, если используется оркестратор). После запуска приложения и всех его служб вы можете выполнять сквозное интеграционное и функциональное тестирование.

Здесь можно использовать несколько методов. В файле docker-compose.yml, который используется для развертывания приложения, на уровне решения можно развернуть точку входа, чтобы можно было использовать тест dotnet. Можно также использовать другой файл Compose, который будет запускать ваши тесты в целевом образе. Используя другой файл Compose для интеграционного тестирования, включающего в себя микрослужбы и базы данных в контейнерах, можно гарантировать, что соответствующие данные будут всегда переведены в исходное состояние перед выполнением тестов.

После того как приложение Compose будет установлено и запущено, вы сможете воспользоваться преимуществами точек останова и исключений, если используется Visual Studio. Интеграционные тесты можно выполнять автоматически в конвейере непрерывной интеграции Azure DevOps Services или в любой другой системе CI/CD, которая поддерживает контейнеры Docker.

Тестирование в eShopOnContainers

Тесты для примера приложения (eShopOnContainers) были недавно реструктуризованы, и теперь существует четыре категории:

  1. модульные тесты, обычные старые модульные тесты, содержащиеся в проектах {MicroserviceName}.UnitTests;

  2. функциональные и интеграционные тесты микрослужб с тестовыми случаями, включающими инфраструктуру для каждой микрослужбы, но изолированные от других и содержащиеся в проектах {MicroserviceName}.FunctionalTests;

  3. функциональные и интеграционные тесты приложений, ориентированные на интеграцию микрослужб, с тестовыми случаями, которые воздействуют на несколько микрослужб (содержатся в проекте Application.FunctionalTests);

Модульные и интеграционные тесты помещены в папку тестов в проекте микрослужб. Но управление тестами приложений и нагрузочными тестами отдельно осуществляется в корневой папке, как показано на рисунке 6-25.

Screenshot of VS pointing out some of the test projects in the solution.

Рис. 6-25. Структура папок тестов в eShopOnContainers

Функциональные и интеграционные тесты микрослужбы и приложения выполняются из Visual Studio с помощью обычного средства запуска тестов. При этом сначала нужно запустить необходимые службы инфраструктуры с помощью набора файлов docker-compose, содержащихся в папке test решения:

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=Pass@word
      - ACCEPT_EULA=Y
    ports:
      - "5433:1433"
  nosqldata:
    ports:
      - "27017:27017"

Таким образом, для запуска функциональных и интеграционных тестов необходимо сначала выполнить следующую команду из папки test решения:

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

Как видите, эти файлы docker-compose всего лишь запускают микрослужбы Redis, RabbitMQ, SQL Server и MongoDB.

Дополнительные ресурсы