You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.Net TestContainers集成测试在Azure Pipeline中连接失败求助

Azure Pipeline中TestContainers集成测试连接SQL Server和RabbitMQ失败排查

本地使用Docker运行TestContainers集成测试一切正常,但在Azure Pipeline执行Run Tests任务时,出现SQL Server和RabbitMQ连接失败的问题。此前使用Docker Compose方式运行集成测试可正常通过。

Azure-pipelines.yml配置

trigger:
- master

pool:
  vmImage: ubuntu-latest

variables:
  buildConfiguration: 'Release'
  BuildParameters.RestoreBuildProjects: '**/*.csproj'
  BuildParameters.TestProjects: |
     $(BuildParameters.RestoreBuildProjects) 
     !**/*IntegrationTests*.csproj
     !**/*FunctionalTests*.csproj

jobs:
- job: BuildAndTest
  displayName: Build and Test

  steps:
  - task: DockerCompose@0
    displayName: "Run a Docker Compose command"
    inputs:
      dockerComposeFile: "docker-compose-integration-tests.yml"
      dockerComposeCommand: "up --abort-on-container-exit" 
    env:
      CONTAINER_NAME: 'bookywooks_orderapi_integrationtests'  

  - task: DotNetCoreCLI@2
    displayName: Restore Nuget Packages 
    inputs:
       command: restore
       projects: $(BuildParameters.RestoreBuildProjects)
       configuration: $(buildConfiguration)  

  - task: DotNetCoreCLI@2
    displayName: 'dotnet build $(buildConfiguration)'
    inputs:
      command: 'build'
      projects: '**/BookyWooks.sln'
      arguments: '--configuration $(buildConfiguration)'

  - script: docker --version
    displayName: 'Print Docker Version'

  - task: DotNetCoreCLI@2
    displayName: Run Tests
    inputs:
      command: test
      projects: $(BuildParameters.TestProjects) 
      arguments: --configuration $(buildConfiguration) --no-build

docker-compose-integration-tests.yml配置

version: '3.4'

services:
  # Service for running integration tests
  bookywooks_orderapi_integrationtests:
    image: ${DOCKER_REGISTRY-}bookywooksorderapiintegrationtests
    build:
      context: .
      dockerfile: BookWooks.OrderApi/src/BookWooks.OrderApi.Web/Dockerfile
      target: integrationtest  # Specify the build target
    depends_on:
      - integrationtestsdatabase  # Depends on the integration tests database service
      - rabbitmq  # Depends on the RabbitMQ service
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - "ConnectionStrings__DefaultConnection=Server=integrationtestsdatabase;Database=BookyWooks.OrderDbIntegrationTests;User=sa;Password=Your_password123; MultipleActiveResultSets=true; TrustServerCertificate=true;"  # Database connection string
      - RabbitMQConfiguration__Config__HostName=${BOOKYWOOKS_RABBITMQ_HOSTNAME:-rabbitmq}  # RabbitMQ host name
      - RabbitMQConfiguration__Config__UserName=${BOOKYWOOKS_RABBITMQ_USERNAME:-guest}  # RabbitMQ user name
      - RabbitMQConfiguration__Config__Password=${BOOKYWOOKS_RABBITMQ_PASSWORD:-guest}  # RabbitMQ password
    volumes:
      - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests  # Mount volume for test results
    entrypoint:
      - dotnet  # Entry point to execute dotnet commands
      - test  # Command to run tests
      - --logger  # Specify logger options
      - trx;LogFileName=/tests/booky-wooks-integration-test-results.xml  # Use trx logger format and specify log file path

  # Service for integration tests database (SQL Server)
  integrationtestsdatabase:
    image: "mcr.microsoft.com/mssql/server:2022-latest"  # Use SQL Server 2022 image
    environment:
       - "SA_PASSWORD=Your_password123"  # Set SA password
       - "ACCEPT_EULA=Y"  # Accept End-User License Agreement
    ports:
    -   "5433:1433"  # Map container port 1433 to host port 5433

  # Service for RabbitMQ
  rabbitmq:
    ports:
      - "15672:15672"  # Map RabbitMQ management port
      - "5672:5672"    # Map RabbitMQ port for message communication
    image: rabbitmq:3-management-alpine  # Use RabbitMQ 3 with management plugin

错误信息

RabbitMQ连接错误

2024-03-12T19:14:08.6922155Z Error creating RabbitMQ connection: None of the specified endpoints were reachable

SQL Server连接错误

2024-03-12T19:14:08.9058528Z    Microsoft.Data.SqlClient.SqlException : A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 35 - An internal exception was caught)

Application Test Factory配置

public class OrderApiApplicationFactory<TEntryPoint> : WebApplicationFactory<Program>, IAsyncLifetime where TEntryPoint : Program
{
    private const string Database = "master";
    private const string Username = "sa";
    private const string Password = "yourStrong(!)Password";
    private string _connectionString;
    private const ushort MsSqlPort = 1433;
    private const ushort RabbitMqPort = 5672; 
    private readonly MsSqlContainer _mssqlContainer;
    private readonly IContainer _rabbitMqContainer;

    public HttpClient HttpClient { get; private set; } = default!;

    private const string RabbitMqUsername = "guest";
    private const string RabbitMqPassword = "guest";

    public OrderApiApplicationFactory()
    {
        _mssqlContainer = new MsSqlBuilder()
            .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
            // Remove static port binding and let Docker assign random ports
            .WithPortBinding(0, MsSqlPort)
            .WithEnvironment("ACCEPT_EULA", "Y") 
            .WithEnvironment("SA_PASSWORD", Password)
            .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlPort))
            .Build();

        _rabbitMqContainer = new RabbitMqBuilder()
            .WithImage("rabbitmq:3-management-alpine") 
            .WithPortBinding(5672, RabbitMqPort) 
            .WithEnvironment("RABBITMQ_DEFAULT_USER", RabbitMqUsername)
            .WithEnvironment("RABBITMQ_DEFAULT_PASS", RabbitMqPassword)
            .WithWaitStrategy(Wait.ForUnixContainer()
                .UntilPortIsAvailable(RabbitMqPort) 
                .UntilPortIsAvailable(15672)) 
            .Build();
    }

    public async Task InitializeAsync()
    {
        await _mssqlContainer.StartAsync();
        await _rabbitMqContainer.StartAsync();
       
        GetDatabaseConnectionString();

        HttpClient = CreateClient();
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration(configurationBuilder =>
        {
            var configuration = new ConfigurationBuilder().AddJsonFile("testcontainersappsettings.json")
                .AddInMemoryCollection(new Dictionary<string, string>
                {
                    ["ConnectionStrings:DefaultConnection"] =  _connectionString,
                    ["RabbitMQConfiguration:Config:HostName"] = _rabbitMqContainer.Hostname
                })
                .AddEnvironmentVariables()
                .Build();

            configurationBuilder.AddConfiguration(configuration);
        });

        builder.ConfigureServices(services =>
        {
            RemoveDbContextOptions<BookyWooksOrderDbContext>(services);
            RemoveDbContextOptions<IntegrationEventLogDbContext>(services);

            AddDbContext<BookyWooksOrderDbContext>(services);
            AddDbContext<IntegrationEventLogDbContext>(services);
        });
    }

    private void RemoveDbContextOptions<T>(IServiceCollection services) where T : DbContext
    {
        var descriptor = services.SingleOrDefault(
            d => d.ServiceType == typeof(DbContextOptions<T>));

        if (descriptor != null)
            services.Remove(descriptor);
    }

    private void AddDbContext<T>(IServiceCollection services) where T : DbContext
    {
        services.AddDbContext<T>(options =>
        {
            options.UseSqlServer(_connectionString,
                builder => builder.MigrationsAssembly(typeof(T).Assembly.FullName));
        });
    }
    
    private string GetDatabaseConnectionString()
    {
        _connectionString = _mssqlContainer.GetConnectionString();
        return _connectionString;
    }

    public new async Task DisposeAsync()
    {
        await _mssqlContainer.DisposeAsync();
        await _rabbitMqContainer.DisposeAsync();
    }
}

排查建议

  • 端口冲突检查:Azure Pipeline的Ubuntu代理可能已有服务占用5672或映射的端口,TestContainers容器无法正常绑定。确认TestContainers使用随机端口(代码中WithPortBinding(0, MsSqlPort)已配置,需验证是否生效),避免固定端口冲突。
  • 服务启动等待策略优化:Azure代理资源有限,SQL Server和RabbitMQ启动速度可能慢于本地。给SQL Server添加等待数据库就绪的策略,比如UntilCommandIsCompleted("SELECT 1");给RabbitMQ添加等待管理API返回成功的检查,确保服务完全启动后再执行测试。
  • 网络模式验证:TestContainers在Azure Pipeline中可能使用了非默认网络,导致测试项目无法访问容器。可以显式指定TestContainers使用Docker桥接网络,或者检查容器的网络配置。
  • 环境变量一致性:TestContainers中的SA密码(yourStrong(!)Password)与Docker Compose中的密码(Your_password123)不一致,虽然TestContainers独立创建容器,但需确认是否有环境变量覆盖了TestContainers的配置。
  • Docker权限验证:确保Azure Pipeline中的执行用户有足够的Docker权限,能够创建和管理TestContainers容器。
  • 版本兼容性检查:对比TestContainers版本与Azure Pipeline中Docker版本的兼容性,参考官方文档确认支持的Docker版本范围。

内容的提问来源于stack exchange,提问作者Ports

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 06:04:54