Контроллеры — это центральный элемент любой службы 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) были недавно реструктуризованы, и теперь существует четыре категории:
- модульные тесты, обычные старые модульные тесты, содержащиеся в проектах {MicroserviceName}.UnitTests;
- функциональные и интеграционные тесты микрослужб с тестовыми случаями, включающими инфраструктуру для каждой микрослужбы, но изолированные от других и содержащиеся в проектах {MicroserviceName}.FunctionalTests;
- функциональные и интеграционные тесты приложений, ориентированные на интеграцию микрослужб, с тестовыми случаями, которые воздействуют на несколько микрослужб (содержатся в проекте Application.FunctionalTests);
Модульные и интеграционные тесты помещены в папку тестов в проекте микрослужб. Но управление тестами приложений и нагрузочными тестами отдельно осуществляется в корневой папке, как показано на рисунке.
Рис. Структура папок тестов в 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.