Выполнение интеграционных тестов ASP.NET Core в пайплайне GitLab
Tags: gitlab, aspnetcore, testing
Published: 2022-12-15 05:50:36

Для того, чтобы запустить в пайплайне интеграционные тесты ASP.NET Core, нужно
- Создать отдельный файл __docker-compose-test.yml__ с конфигурацией развёртывания базы данных. Этот файл не будет использоваться при основном развёртывании, а только при выполнении тестов. 
- Запустить контейнер __dotnet/sdk__, связать директорию в контейнере с директорией приложения (вероятно это текущая директория), назначить в качестве рабочей директории докера __workdir__ директорию с файлами тестов и выполнить команду __dotnet test__. При этом переопределить строку подключения к базе данных, так как по умолчанию используется строка подключения из файла __appsettings.json__
- Выполнить команду __docker-compose down__

На что нужно обратить внимание:
- В интеграционных тестах класс с методами тестов обычно наследуется от __WebApplicationFactory<Startup>__ или __WebApplicationFactory<Program>__, и при создании класса с тестами выполняются методы класса __Startup (Program)__. Если классов с тестами несколько, то методы класса Startup будут выполняться каждый раз, и если в нём определены операции миграции базы данных, будет возникать ошибка. Причина ошибки в том, что каждый класс с тестами запускается как бы отдельно и «приложение не знает», какие миграции уже были применены к базе данных до этого. Получается, что все миграции применяются каждый раз. 
Чтобы этого избежать, можно поместить все тесты в один класс, так, чтобы создавался только один класс __WebApplicationFactory__ и соответственно методы класса __Startup__ выполнялись только один раз 

## Примеры кода

### Интеграционный тест
```
using Microsoft.AspNetCore.Mvc.Testing;

public class IntegrationTests:IClassFixture<WebApplicationFactory<Startup>>{
    private readonly WebApplicationFactory<Startup> _fixture;
    public IntegrationTests(WebApplicationFactory<Startup> fixture)
    {
        _fixture = fixture;
    }
    [Fact]
    public void Test1(){}
    [Fact]
    public void Test2(){}
    [Fact]
    public void Test3(){}
    [Fact]
    public void Test4(){}
}
```
### Файл docker-compose-test.yml с образом postgres
```
version: '3.4'
networks: 
  network_name:
    driver: bridge
services:
  db:
   image: postgres:14
   ports:
    - "5432:5432"
   environment:
    PGUSER: postgres
    POSTGRES_PASSWORD: postgres
    POSTGRES_DB: postgres
   networks:
    - network_name
   restart: always
   healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 20s
      timeout: 10s
      retries: 3
      start_period: 10s  
```
### Джоб в конфигурации .gitlab-ci.yml
```
stages:
  - test
  - build
  - deploy

docker-test:
  image: docker:latest
  stage: test
  when: on_success
  services:
    - docker:dind
  script:
# развернуть контейнер с базой данных 
# -d - отменить вывод журнала в консоль для того, чтобы остальные команды могли выполниться
    - docker-compose --file docker-compose-test.yml up -d --build
# --name - задать имя контейнеру (необязательно)
# --network - задать имя сети, с которой будет соединяться контейнер. При развёртывании контейнера с базой данных в предыдущем шаге создаётся сеть с именем network_name. При развёртывании контейнера с базой данных к этому имени добавится имя текущей директории, из которой была выполнена команда docker-compose, так что окончательное имя сети будет не таким, как указано в файле docker-compose-test, а с префиксом из имени текущей директории и нижним подчёркиванием: [имя текущей директории]_network_name. Нужно иметь это ввиду при запуске команды.
# --rm - удалить образ, который используется при развёртывании контейнера а также удалить связанные volume
# -w - задать рабочую директорию для контейнера, то есть ту директорию, в которой будут выполняться дальнейшие команды. Это то же самое, что команда WORKDIR в файлах Dockerfile. Здесь мы в качестве рабочей директории задаём директорию Tests, так как именно в ней нужно будет выполнить команду dotnet test
# dotnet test - команда запуска тестов
# -e задать переменную текущей среды выполнения. Здесь мы задаём строку соединения с базой данных.
# в строке соединения к базе данных:
# __ - двойное подчёркивание используется вместо двоеточия :, так как двоеточие не всегда срабатывает
# Server=db - здесь db - это имя службы из участка кода
# services:
# db:
# image: postgres:14          
    - docker run --name app_name --network [имя текущей директории]_network_name --rm -v $(pwd):/app -w /app/Tests mcr.microsoft.com/dotnet/sdk:7.0 dotnet test -e "ConnectionStrings__DefaultConnection=Server=db;Port=5432;Database=postgres;Username=postgres;Password=postgres"
    - docker-compose --file docker-compose-test.yml down -v

docker-build:
  stage: build
# …
docker-deploy:
  stage: deploy
# …
```