Поделиться через


Модульные тесты и тесты интеграции в минимальных приложениях API

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.

Внимание

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

В текущем выпуске см . версию .NET 9 этой статьи.

Фияз Бен Хасан, и Рик Андерсон

Общие сведения об интеграционных тестах

Интеграционные тесты служат для оценки компонентов приложения на более широком уровне, чем модульные тесты. Модульные тесты используются для тестирования изолированных программных компонентов, таких как отдельные методы класса. Интеграционные тесты позволяют убедиться, что два или несколько компонентов приложения работают совместно для получения ожидаемого результата, включая, возможно, все компоненты, необходимые для полной обработки запроса.

Эти более широкие тесты используются для тестирования инфраструктуры приложения и всей платформы, зачастую включая следующие компоненты:

  • База данных
  • Файловая система
  • Сетевые устройства
  • Конвейер "запрос-ответ"

В модульных тестах вместо компонентов инфраструктуры используются структурные компоненты, известные как имитации или макеты объектов.

В отличие от модульных тестов, интеграционные тесты:

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

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

В обсуждениях тестов интеграции тесты тесты, тестируемый проект часто называется системным тестом или "SUT" для краткого. SUT используется в этой статье для ссылки на тестируемое приложение ASP.NET Core.

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

Интеграционные тесты ASP.NET Core

Для интеграционных тестов в ASP.NET Core требуется следующее:

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

Интеграционные тесты придерживаются последовательности событий из обычных шагов теста Подготовка, Выполнение и Проверка.

  1. Настраивается веб-узел ТС.
  2. Создается клиент тестового сервера для отправки запросов к приложению.
  3. Выполняется шаг "Упорядочить" — тестовое приложение подготавливает запрос.
  4. Выполняется шаг теста Act: клиент отправляет запрос и получает ответ.
  5. Выполняется шаг теста Assert: фактический ответ проверяется как проход или сбой на основе ожидаемого ответа.
  6. Процесс продолжается до тех пор, пока не будут выполнены все тесты.
  7. Выводятся результаты теста.

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

Компоненты инфраструктуры, такие как тестовый веб-узел и сервер тестирования в памяти (TestServer), предоставляются или управляются пакетом Microsoft.AspNetCore.Mvc.Testing. Использование этого пакета упрощает создание и выполнение тестов.

Пакет Microsoft.AspNetCore.Mvc.Testing выполняет следующие задачи:

  • Копирует файл зависимостей (.deps) из SUT в каталог тестового проекта bin .
  • Задает корневой каталог содержимого в корне проекта ТС, чтобы при выполнении тестов были найдены статические файлы и страницы или представления.
  • Предоставляет класс WebApplicationFactory для упрощения начальной загрузки ТС с TestServer.

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

Отделяйте модульные тесты от тестов интеграции в разные проекты. Разделение тестов:

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

Пример кода на GitHub содержит пример модульных и интеграционных тестов в приложении API "Минимальный".

Типы реализации IResult

Общедоступные IResult Microsoft.AspNetCore.Http.HttpResults типы реализации в пространстве имен можно использовать для модульного тестирования минимальных обработчиков маршрутов при использовании именованных методов вместо лямбда-кодов.

Следующий код использует 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);
}

Следующий код использует 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);
}

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