mcpField
T
testcontainers-integration-tests

testcontainers-integration-tests

github.com/aaronontheweb测试与质量1.2k555 次安装今天更新
代码仓库

关于 testcontainers-integration-tests

testcontainers-integration-tests 是一项 .NET 集成测试技能,指导开发者使用容器中的真实基础设施,而非模拟对象。它基于 TestContainers 4.x,涵盖数据库、缓存和消息队列测试,以及隔离和清理方法。

主要功能

  1. 01使用容器化的真实基础设施
  2. 02涵盖 SQL Server、PostgreSQL、Redis 和 RabbitMQ
  3. 03提供隔离与容器生命周期指导
  4. 04包含迁移和数据库重置示例
  5. 05提供 CI 配置和性能建议

适用场景

  1. 01针对真实数据库测试数据访问
  2. 02验证 Redis 或 RabbitMQ 集成
  3. 03检查迁移、约束和事务
  4. 04在支持 Docker 的 CI 中运行集成测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/aaronontheweb/dotnet-skills --skill testcontainers-integration-tests

安装后在 Claude Code 中输入/testcontainers-integration-tests确认已加载。

附带文件database-patterns.mdinfrastructure-patterns.md

详细介绍

使用 TestContainers 进行集成测试

何时使用此技能

在以下情况下使用此技能:

  • 编写需要真实基础设施(数据库、缓存、消息队列)的集成测试
  • 针对实际数据库测试数据访问层
  • 验证消息队列集成
  • 测试 Redis 缓存行为
  • 避免为基础设施组件使用模拟对象
  • 确保测试在类似生产环境的环境中运行
  • 测试数据库迁移和架构变更

参考文件

核心原则

  1. 使用真实基础设施,而非模拟对象 - 在容器中使用实际的数据库/服务,而不是模拟对象
  2. 测试隔离 - 每项测试都使用全新的容器或全新的数据
  3. 自动清理 - TestContainers 负责容器生命周期和清理
  4. 快速启动 - 适当时在同一类的测试之间复用容器
  5. 兼容 CI/CD - 在启用了 Docker 的 CI 环境中无缝运行
  6. 端口随机化 - 容器使用随机端口以避免冲突

TestContainers 4.x API

此技能面向 TestContainers 4.x,使用 3.0+ 模块构建器 API。3.0 之前的类型已重命名并移除,因此使用 TestcontainersBuilder<T> 或 TestcontainersContainer 的代码无法通过现代程序包编译:

旧版(3.0 之前)新版(3.0+)
new TestcontainersBuilder<T>()模块构建器(例如 new PostgreSqlBuilder(...))或 new ContainerBuilder(...)
TestcontainersContainerDockerContainer / 模块容器(例如 PostgreSqlContainer)
TestcontainersNetworkBuilderNetworkBuilder
GetMappedPublicPort(port)GetMappedPublicPort(port)(仍可用)

为什么使用 4.x,而不是 3.x? Testcontainers 3.0.0 中的模块构建器没有提供接受镜像的构造函数。这些构造函数(以及无参数构建器构造函数的弃用)是在 4.x 系列中引入的,因此此处示例依赖 4.x 的行为。若要确保构建可复现,请将模块程序包固定为 4.x(或更高)版本,而不要使用不受限制的通配符。

推荐使用 Testcontainers.<Provider> 程序包中的模块构建器(请参阅必需的 NuGet 程序包)。每个模块构建器都会为相应数据库或服务预先配置合适的镜像、环境变量、端口和等待策略。

将镜像传递给构建器的构造函数(而不是通过 .WithImage()),以避免使用已过时的无参数构造函数:

var container = new PostgreSqlBuilder("postgres:latest")
    .WithDatabase("testdb")
    .WithUsername("postgres")
    .WithPassword("postgres")
    .Build();

为什么选择 TestContainers,而不是模拟对象?

模拟基础设施的问题
// BAD: Mocking a database
public class OrderRepositoryTests
{
    private readonly Mock<IDbConnection> _mockDb = new();

    [Fact]
    public async Task GetOrder_ReturnsOrder()
    {
        // This doesn't test real SQL behavior, constraints, or performance
        _mockDb.Setup(db => db.QueryAsync<Order>(It.IsAny<string>()))
            .ReturnsAsync(new[] { new Order { Id = 1 } });

        var repo = new OrderRepository(_mockDb.Object);
        var order = await repo.GetOrderAsync(1);

        Assert.NotNull(order);
    }
}

问题:无法测试实际 SQL 查询,会遗漏约束/索引,造成虚假的信心,也无法发现 SQL 语法错误。

更好的选择:使用 TestContainers 和真实数据库
// GOOD: Testing against a real database
public class OrderRepositoryTests : IAsyncLifetime
{
    private readonly MsSqlContainer _dbContainer;
    private SqlConnection _connection;

    public OrderRepositoryTests()
    {
        _dbContainer = new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-latest")
            .WithDatabase("TestDb")
            .WithPassword("Your_password123")
            .Build();
    }

    public async Task InitializeAsync()
    {
        await _dbContainer.StartAsync();
        _connection = new SqlConnection(_dbContainer.GetConnectionString());
        await _connection.OpenAsync();
        await RunMigrationsAsync(_connection);
    }

    public async Task DisposeAsync()
    {
        await _connection.DisposeAsync();
        await _dbContainer.DisposeAsync();
    }

    [Fact]
    public async Task GetOrder_WithRealDatabase_ReturnsOrder()
    {
        await _connection.ExecuteAsync(
            "INSERT INTO Orders (Id, CustomerId, Total) VALUES (1, 'CUST1', 100.00)");

        var repo = new OrderRepository(_connection);
        var order = await repo.GetOrderAsync(1);

        Assert.NotNull(order);
        Assert.Equal("CUST1", order.CustomerId);
        Assert.Equal(100.00m, order.Total);
    }
}

完整的 SQL Server、PostgreSQL 和迁移测试示例,请参阅 database-patterns.md。

Redis、RabbitMQ、多容器网络、容器复用和 Respawn 数据库重置模式,请参阅 infrastructure-patterns.md。

必需的 NuGet 程序包

使用模块程序包——每个提供程序使用一个。这些程序包可替代你原本需要针对核心 Testcontainers 程序包编写的特殊处理:

<ItemGroup>
  
  <PackageReference Include="Testcontainers" Version="*" />

  
  <PackageReference Include="Testcontainers.MsSql" Version="*" />
  <PackageReference Include="Testcontainers.PostgreSql" Version="*" />
  <PackageReference Include="Testcontainers.Redis" Version="*" />
  <PackageReference Include="Testcontainers.RabbitMq" Version="*" />

  <PackageReference Include="xunit" Version="*" />
  <PackageReference Include="xunit.runner.visualstudio" Version="*" />

  
  <PackageReference Include="Microsoft.Data.SqlClient" Version="*" />
  <PackageReference Include="Npgsql" Version="*" /> 

  
  <PackageReference Include="StackExchange.Redis" Version="*" /> 
  <PackageReference Include="RabbitMQ.Client" Version="*" /> 
</ItemGroup>

必需的 using 指令

模块构建器和容器类型位于特定于提供程序的命名空间中。基础类型(ContainerBuilder、NetworkBuilder、Wait)位于 DotNet.Testcontainers.Builders 中,INetwork 位于 DotNet.Testcontainers.Networks 中:

using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Networks;
using Testcontainers.MsSql;
using Testcontainers.PostgreSql;
using Testcontainers.Redis;
using Testcontainers.RabbitMq;

最佳实践

  1. 始终使用 IAsyncLifetime - 正确进行异步初始化和清理
  2. 等待端口可用 - 使用 Wait 策略确保容器已就绪
  3. 使用随机端口 - 让 TestContainers 自动分配端口
  4. 在测试之间清理数据 - 使用全新容器或清空表
  5. 尽可能复用容器 - 比每次测试都创建新容器更快
  6. 测试真实查询 - 不要只测试模拟对象;验证实际 SQL 行为
  7. 验证约束 - 测试外键、唯一约束和索引
  8. 测试事务 - 验证回滚和提交行为
  9. 使用真实数据 - 使用接近生产环境数据量的数据进行测试
  10. 处理清理工作 - 始终在 DisposeAsync 中释放容器

常见问题和解决方案

容器启动超时
_container = new PostgreSqlBuilder("postgres:latest")
    .WithDatabase("testdb")
    .WithUsername("postgres")
    .WithPassword("postgres")
    .WithWaitStrategy(Wait.ForUnixContainer()
        .UntilInternalTcpPortIsAvailable(5432, w => w.WithTimeout(TimeSpan.FromMinutes(2))))
    .Build();
端口已被占用

始终使用随机端口映射。模块构建器默认会处理此问题,但核心容器构建器会显式公开此设置:

.WithPortBinding(5432, true) // true = assign random public port
容器未清理

确保正确释放:

public async Task DisposeAsync()
{
    await _connection?.DisposeAsync();
    await _container?.DisposeAsync();
}
测试在 CI 中失败,但在本地通过

确保 CI 支持 Docker:

# GitHub Actions
runs-on: ubuntu-latest # Has Docker pre-installed

CI/CD 集成

GitHub Actions
name: Integration Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 9.0.x

    - name: Run Integration Tests
      run: |
        dotnet test tests/YourApp.IntegrationTests \
          --filter Category=Integration \
          --logger trx

    - name: Cleanup Containers
      if: always()
      run: docker container prune -f

性能提示

  1. 复用容器 - 在一个集合中的多个测试之间共享固定装置
  2. 使用 Respawn - 无需重新创建容器即可重置数据
  3. 并行执行 - TestContainers 会自动处理端口冲突
  4. 使用轻量级镜像 - Alpine 版本体积更小、速度更快
  5. 缓存镜像 - Docker 会在本地缓存已拉取的镜像

评论

支持 Markdown