在使用 testcontainers 的 docker 中运行 dotnet 测试

Joa*_*iro 8 .net docker dockerfile testcontainers

我有一个集成测试项目,它在 VS 中按预期执行。集成测试使用 MsSql 测试容器(来自https://dotnet.testcontainers.org/)。

我的目标是在 Docker 映像内的 Azure DevOps 管道中运行这些测试,就像我在其他不使用测试容器的项目中成功执行的那样。现在我只是尝试在本地计算机的 docker 映像中运行测试。不幸的是我面临连接问题。

我的环境:

  • .NET 6
  • 操作系统:Windows
  • 带有 Linux 容器的 Docker 桌面

我的代码:

Authentication.Api/MyProject.Authentication.Api/Dockerfile:

##########################################################
# build

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["Authentication.Api/MyProject.Authentication.Api/MyProject.Authentication.Api.csproj", "Authentication.Api/MyProject.Authentication.Api/"]
COPY ["Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj", "Authentication.Api/MyProject.Authentication.Api.IntegrationTests/"]
RUN dotnet restore "Authentication.Api/MyProject.Authentication.Api/MyProject.Authentication.Api.csproj"
RUN dotnet restore "Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj"
COPY . .

WORKDIR "/src/Authentication.Api/MyProject.Authentication.Api"
RUN dotnet build "MyProject.Authentication.Api.csproj" -c Release -o /app/build

WORKDIR "/src/Authentication.Api/MyProject.Authentication.Api.IntegrationTests"
RUN dotnet build -c Release

##########################################################
# run test projects

FROM build AS tests
WORKDIR /src
VOLUME /var/run/docker.sock:/var/run/docker.sock
RUN dotnet test --no-build -c Release --results-directory /testresults --logger "trx;LogFileName=testresults_authentication_api_it.trx" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2cCobertura /p:CoverletOutput=/testresults/coverage/ -p:MergeWith=/testresults/coverage/coverage.json  Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj

##########################################################
# create image

FROM build AS publish
WORKDIR "/src/Authentication.Api/MyProject.Authentication.Api"
RUN dotnet publish "MyProject.Authentication.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS final
WORKDIR /app
EXPOSE 80
EXPOSE 443
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyProject.Authentication.Api.dll"]
Run Code Online (Sandbox Code Playgroud)

Authentication.Api/MyProject.Authentication.Api.IntegrationTests/Factory/CustomWebApplicationFactory.cs:

public class CustomWebApplicationFactory<TProgram, TDbContext> : WebApplicationFactory<TProgram>, IAsyncLifetime, ICustomWebApplicationFactory
    where TProgram : class
    where TDbContext : DbContext
{
    private readonly MsSqlDatabaseProvider _applicationMsSqlDatabaseProvider;

    public CustomWebApplicationFactory()
    {
        _applicationMsSqlDatabaseProvider = new MsSqlDatabaseProvider();
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
        => builder.ConfigureServices(services =>
        {
            services.Remove(services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>)) ?? throw new InvalidOperationException());
            services.AddDbContext<ApplicationDbContext>(options => { options.UseSqlServer(_applicationMsSqlDatabaseProvider.Database.ConnectionString); });

            ServiceProvider? sp = services.BuildServiceProvider();
            using IServiceScope scope = sp.CreateScope();
            IServiceProvider scopedServices = scope.ServiceProvider;
            ILogger<CustomWebApplicationFactory<TProgram, TDbContext>> logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TProgram, TDbContext>>>();

            ApplicationDbContext applicationDbContext = scopedServices.GetRequiredService<ApplicationDbContext>();
            applicationDbContext.Database.EnsureCreated();
            logger.LogInformation("Ensured that the ApplicationDbContext DB is created.");
        });

    public async Task InitializeAsync() =>
        await _applicationMsSqlDatabaseProvider.Database.StartAsync();

    public new async Task DisposeAsync() =>
        await _applicationMsSqlDatabaseProvider.Database.DisposeAsync().AsTask();
}
Run Code Online (Sandbox Code Playgroud)

{共享库路径}/MsSqlDatabaseProvider.cs:

public class MsSqlDatabaseProvider
{
    private const string DbPassword = "my_dummy_password#123";
    private const string DbImage = "mcr.microsoft.com/mssql/server:2019-latest";

    public readonly TestcontainerDatabase Database;

    public MsSqlDatabaseProvider() =>
        Database = new TestcontainersBuilder<MsSqlTestcontainer>()
            .WithDatabase(new MsSqlTestcontainerConfiguration
            {
                Password = DbPassword,
            })
            .WithImage(DbImage)
            .WithCleanUp(true)
            .Build();
}
Run Code Online (Sandbox Code Playgroud)

在命令行上我运行docker build --progress=plain -f Authentication.Api\MyProject.Authentication.Api\Dockerfile --target tests --tag myproject-tests ..

我收到以下错误:

无法检测到 Docker 端点。使用环境变量或 ~/.testcontainers.properties 文件来自定义配置:https://dotnet.testcontainers.org/custom_configuration/(参数“DockerEndpointAuthConfig”)

我尝试在docker中添加环境变量,将dockerfile更改为

RUN export DOCKER_HOST="tcp://192.168.99.100:2376" && dotnet test --no-build -c Release --results-directory /testresults --logger "trx;LogFileName=testresults_authentication_api_it.trx" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2cCobertura /p:CoverletOutput=/testresults/coverage/ -p:MergeWith=/testresults/coverage/coverage.json  Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj
Run Code Online (Sandbox Code Playgroud)

并添加.WithDockerEndpoint("tcp://192.168.99.100:2376")MsSqlDatabaseProvider,但我最终遇到了另一个错误:

System.Net.Http.HttpRequestException:连接失败

System.Net.Sockets.SocketException:连接被拒绝

我不知道应该为 docker 主机/docker 端点使用什么值。或者解决方案是别的?

先感谢您!

Joa*_*iro 6

我可以设法做到这一点,但有两个主要区别:

  1. 测试不在 docker 映像上运行,而是在 docker 容器上运行。
  2. docker compose现在正在使用。

docker-compose-tests.yml:

version: '3.4'

services:
  myproject.authentication.api.tests: # docker compose -f docker-compose-tests.yml up myproject.authentication.api.tests
    build:
      context: .
      dockerfile: Authentication.Api/MyProject.Authentication.Api/Dockerfile
      target: build
    command: >
        sh -cx "
                dotnet test /src/Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj -c Release --results-directory /testresults --logger \"trx;LogFileName=testresults_authentication_api_it.trx\" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2cCobertura /p:CoverletOutput=/testresults/coverage/ -p:MergeWith=/testresults/coverage/coverage.json"
    environment:
      - TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal # Needed in Docker Desktop (Windows), needs to be removed on linux hosts. Can be done with a override compose file.
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - coverage:/testresults/coverage
    container_name: myproject.authentication.api.tests
Run Code Online (Sandbox Code Playgroud)

(如果预计要运行更多测试项目,“sh”命令很有用。)

Authentication.Api/MyProject.Authentication.Api/Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["Authentication.Api/MyProject.Authentication.Api/MyProject.Authentication.Api.csproj", "Authentication.Api/MyProject.Authentication.Api/"]
COPY ["Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj", "Authentication.Api/MyProject.Authentication.Api.IntegrationTests/"]
RUN dotnet restore "Authentication.Api/MyProject.Authentication.Api/MyProject.Authentication.Api.csproj"
RUN dotnet restore "Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj"
COPY . .

WORKDIR "/src/Authentication.Api/MyProject.Authentication.Api"
RUN dotnet build "MyProject.Authentication.Api.csproj" -c Release -o /app/build

WORKDIR "/src/Authentication.Api/MyProject.Authentication.Api.IntegrationTests"
RUN dotnet build -c Release
Run Code Online (Sandbox Code Playgroud)

Authentication.Api/MyProject.Authentication.Api.IntegrationTests/Factory/CustomWebApplicationFactory.cs:与问题中相同。

{共享库路径}/MsSqlDatabaseProvider.cs:

public class MsSqlDatabaseProvider
{
    private const string DbImage = "mcr.microsoft.com/mssql/server:2019-latest";
    private const string DbUsername = "sa";
    private const string DbPassword = "my_dummy_password#123";
    private const ushort MssqlContainerPort = 1433;


    public readonly TestcontainerDatabase Database;

    public MsSqlDatabaseProvider() =>
        Database = new TestcontainersBuilder<MsSqlTestcontainer>()
            .WithDatabase(new MsSqlTestcontainerConfiguration
            {
                Password = DbPassword,
            })
            .WithImage(DbImage)
            .WithCleanUp(true)
            .WithPortBinding(MssqlContainerPort, true)
            .WithEnvironment("ACCEPT_EULA", "Y")
            .WithEnvironment("MSSQL_SA_PASSWORD", DbPassword)
            .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("/opt/mssql-tools/bin/sqlcmd", "-S", $"localhost,{MssqlContainerPort}", "-U", DbUsername, "-P", DbPassword))
            .Build();
}
Run Code Online (Sandbox Code Playgroud)

我可以使用 docker 在 docker 中运行测试docker compose -f docker-compose-tests.yml up myproject.authentication.api.tests


And*_*ter 5

免责声明

我是 Testcontainers for .NET 的维护者,并在 AtomicJar(Testcontainers 和 Testcontainers Cloud 背后的公司)担任工程师


在 Docker 镜像构建中运行测试容器非常困难。提供对在进程之外运行且独立于进程的 Docker 端点的访问具有挑战性(但可能) docker build。通常,它需要复杂的设置。作为一种更简单的解决方案,利用Testcontainers Cloud作为 Docker 镜像构建的一部分效果出奇的好。以下 Dockerfile 配置在 Docker 映像构建中运行 Testcontainers Cloud 代理 (L:5):

FROM mcr.microsoft.com/dotnet/sdk:7.0
ARG TC_CLOUD_TOKEN
WORKDIR /tests
COPY . .
RUN TC_CLOUD_TOKEN=$TC_CLOUD_TOKEN curl -fsSL https://app.testcontainers.cloud/bash | bash && dotnet test
Run Code Online (Sandbox Code Playgroud)

运行会docker build --build-arg TC_CLOUD_TOKEN=${TC_CLOUD_TOKEN} .在 Testcontainers Cloud 中启动测试依赖项。我对mssql/server:2022-latest容器进行了一个简单的测试:

FROM mcr.microsoft.com/dotnet/sdk:7.0
ARG TC_CLOUD_TOKEN
WORKDIR /tests
COPY . .
RUN TC_CLOUD_TOKEN=$TC_CLOUD_TOKEN curl -fsSL https://app.testcontainers.cloud/bash | bash && dotnet test
Run Code Online (Sandbox Code Playgroud)

确保您使用的是多阶段构建,并且不要在层中公开您的令牌。