Введение

Внедрение обязательных тестов в конвейеры CI/CD необходимо для раннего обнаружения потенциальных проблем и проверки того, что код делает то, что должен делать.

Запуск тестов для контейнерных служб имеет веские аргументы в пользу:

  • совместимость с окружающей средой;
  • Тесты изолированы друг от друга — нет загрязнения данных;
  • Простота настройки конвейеров CI/CD;

Отказ от ответственности

В этой статье мы приведем пример запуска интеграционных тестов общедоступных и частных образов Docker с помощью Azure Pipelines.

Репозиторий демонстрационного приложения можно найти здесь — для запуска тестов должен быть установлен Docker (в этом примере используются контейнеры Linux).

Эти примеры будут созданы с использованием .NET 6. Если у вас нет учетной записи Azure DevOps, вы можете ее создать. После этого вам нужно будет запросить параллельный агент, чтобы на нем могли работать ваши пайплайны — https://aka.ms/azpipelines-parallelism-request

Конвейеры, использующие общедоступные образы Docker

В BaseFixture.cs для каждого выполняемого теста создается и запускается экземпляр TestContainerBuilder. Это создаст экземпляр контейнера Postgres Docker и в конце выполнения теста очистит его.

private static readonly string IMAGE_NAME = "postgres:latest";
private static readonly string POSTGRES_USER = Guid.NewGuid().ToString("N");
private static readonly string POSTGRES_PASSWORD = Guid.NewGuid().ToString("N");
private static readonly int Port = Random.Shared.Next(12000, 16000);
protected static readonly string POSTGRES_DB = Guid.NewGuid().ToString("N");

ITestcontainersBuilder<TestcontainersContainer> _containerBuilder = new TestcontainersBuilder<TestcontainersContainer>()
  .WithImage(IMAGE_NAME)
  .WithEnvironment(nameof(POSTGRES_USER), POSTGRES_USER)
  .WithEnvironment(nameof(POSTGRES_PASSWORD), POSTGRES_PASSWORD)
  .WithEnvironment(nameof(POSTGRES_DB), POSTGRES_DB)
  .WithPortBinding(Port, 5432)
  .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(5432));

При выполнении тестов мы видим, что наш первый тест выполняется для контейнера с именем nostalgic_germain. После выполнения теста контейнер удаляется и создается новый, youthful_keldysh для второго теста:

Супер! Теперь осталось только запихнуть код в репозиторий DevOps — вы можете посмотреть, как это сделать здесь. После отправки кода в вашем проекте DevOps должно появиться что-то похожее на это изображение (щелкнув меню Репозитории):

На снимке экрана выше видно, что конвейер уже создан (azure-pipelines.yml). Поскольку у вас его еще нет, давайте создадим его.
В меню слева выберите раздел Конвейеры, нажмите кнопку Новый конвейер (вверху справа ), выберите Azure Repos Git, проверьте доступный репозиторий, а затем Начальный конвейер.

После этого вы можете скопировать и вставить эту довольно простую структуру, которая будет выполнять любые найденные тесты на доступном решении.

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: UseDotNet@2
  inputs:
    version: '6.x'
- task: DotNetCoreCLI@2
  inputs:
    command: 'test'
    projects: '**/*.csproj'

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

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

Конвейеры, использующие приватные образы Docker

Используя тот же демонстрационный проект, вы можете увидеть на PrivateRegistryTests.cs, что мы используем образ из частного центрального репозитория. Этот образ был создан из простого минимального API, который имеет только одну конечную точку:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/hello/{name}", (string name) =>
{
    return $"Hello {name}";
});

app.Run();

Локально это будет работать, поскольку я являюсь владельцем репозитория (и мой экземпляр Docker вошел в систему с учетной записью, которой принадлежит этот репозиторий).

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

Сначала нам нужно добавить новое сервисное подключение (позволяющее удаленное подключение к внешней службе для наших конвейеров) для нашего реестра Docker, и для этого перейдите в Настройки проекта -> Сервисные подключения и нажмите в правом верхнем углу. Кнопка Новое подключение к службе. Выберите параметр Реестр Docker, затем выберите Docker Hub. Заполните обязательные поля, установите флажок Предоставить разрешение на доступ ко всем конвейерам и завершите действие, нажав Подтвердить и сохранить.

Теперь единственное, чего не хватает, — это обновить наш конвейер, чтобы иметь возможность войти в нашу новую службу для успешного выполнения тестов — поэтому для этого нам нужно только добавить новую задачу с командой входа в систему — например так:

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: UseDotNet@2
  inputs:
    version: '6.x'

- task: Docker@2
  inputs:
    containerRegistry: 'Private Docker'
    command: 'login'

- task: DotNetCoreCLI@2
  inputs:
    command: 'test'
    projects: '**/*.csproj'

Это позволит войти в наш частный концентратор реестра, что позволит успешно получить частный образ для бесперебойной работы наших интеграционных тестов:

Заключительные примечания

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

Учитывая это, каков ваш подход к тестам? С контейнерами или без?