在docker中使用testcontainer运行dotnet测试



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

我的目标是在一个docker镜像中的Azure DevOps管道中运行这些测试,就像我在其他不使用testcontainer的项目中成功做的那样。现在,我只是尝试在本地机器上的docker映像中运行测试。不幸的是,我正面临连接问题。

我的环境:

  • 。网6
  • 操作系统:Windows
  • Docker Desktop with linux containers

我的代码:

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"]

Authentication.Api/MyProject.Authentication.Api.IntegrationTests/工厂/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();
}

{共享库路径}/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();
}

在命令行中运行docker build --progress=plain -f Authentication.ApiMyProject.Authentication.ApiDockerfile --target tests --tag myproject-tests .

,我得到以下错误:

无法检测Docker端点。使用环境变量或~/.testcontainers。属性文件来定制您的配置: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

和添加.WithDockerEndpoint("tcp://192.168.99.100:2376")在MsSqlDatabaseProvider,但我最终与另一个错误:

System.Net.Http。HttpRequestException: Connection failed

System.Net.Sockets。SocketException: Connection refused

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

提前感谢!

免责声明:

我是。net Testcontainers的维护者,也是AtomicJar的工程师,该公司开发了Testcontainers和Testcontainers Cloud。


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

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

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


namespace DockerImageBuild.Test;
using System.Data.SqlClient;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using Microsoft.Extensions.Logging;
using Xunit;
public class UnitTest1 : IAsyncLifetime
{
private const string Database = "master";
private const string Username = "sa";
private const string Password = "yourStrong(!)Password";
private const ushort MssqlContainerPort = 1433;
private readonly TestcontainersContainer _dbContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.WithPortBinding(MssqlContainerPort, true)
.WithEnvironment("ACCEPT_EULA", "Y")
.WithEnvironment("MSSQL_SA_PASSWORD", Password)
.WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("/opt/mssql-tools/bin/sqlcmd", "-S", $"localhost,{MssqlContainerPort}", "-U", Username, "-P", Password))
.Build();
[Fact]
public Task Test1()
{
var connectionString = $"Server={_dbContainer.Hostname},{_dbContainer.GetMappedPublicPort(MssqlContainerPort)};Database={Database};User Id={Username};Password={Password};";
using (var sqlConnection = new SqlConnection(connectionString))
{
try
{
sqlConnection.Open();
}
catch
{
Assert.Fail("Could not establish database connection.");
}
finally
{
TestcontainersSettings.Logger.LogInformation(connectionString);
}
}
return Task.CompletedTask;
}
public Task InitializeAsync()
{
return _dbContainer.StartAsync();
}
public Task DisposeAsync()
{
return _dbContainer.DisposeAsync().AsTask();
}
}

确保你使用的是多阶段构建,而不是在一个层中暴露你的令牌。

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

  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

("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

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();
}

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

最新更新