SuperAPI AI 工具集

testcontainers

测试与质量

查看项目

更新于 10-15 18:09

技能介绍

用途

Testcontainers 技能用于在 .NET 集成测试中启动真实的依赖服务,例如数据库、缓存和消息队列。它适合在以下情况使用:编写需要真实基础设施的集成测试、针对实际数据库测试数据访问层、验证消息队列集成、测试 Redis 缓存行为、避免对基础设施组件使用模拟对象、确保测试在接近生产的环境中运行,以及测试数据库迁移和架构变更。

主要能力

该技能的核心原则包括:

  • 真实基础设施优先于模拟对象:在容器中使用实际的数据库和服务,而不是 Mock。
  • 测试隔离:每个测试获得全新的容器或全新的数据。
  • 自动清理:Testcontainers 负责容器的生命周期和清理。
  • 快速启动:在适当情况下,同一测试类中的多个测试可以复用容器。
  • CI/CD 兼容:在支持 Docker 的 CI 环境中可无缝运行。
  • 端口随机化:容器使用随机端口以避免冲突。

技能中说明了为什么用 Testcontainers 替代 Mock:模拟基础设施无法测试真实的 SQL 行为、约束、索引和性能,可能带来虚假的信心,也无法捕获 SQL 语法错误或架构不匹配。而使用真实数据库的 Testcontainers 可以测试真实的 SQL 查询和数据库行为,捕获约束违规、索引问题和性能问题,验证迁移是否正确工作,从而对数据访问层提供真实的信心。

使用方式

技能提供了使用 Testcontainers 的示例代码。以下是一个针对 SQL Server 的测试类示例:

public class OrderRepositoryTests : IAsyncLifetime
{
    private readonly TestcontainersContainer _dbContainer;
    private IDbConnection _connection;

    public OrderRepositoryTests()
    {
        _dbContainer = new TestcontainersBuilder<TestcontainersContainer>()
            .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
            .WithEnvironment("ACCEPT_EULA", "Y")
            .WithEnvironment("SA_PASSWORD", "Your_password123")
            .WithPortBinding(1433, true)
            .Build();
    }

    public async Task InitializeAsync()
    {
        await _dbContainer.StartAsync();

        var port = _dbContainer.GetMappedPublicPort(1433);
        var connectionString = $"Server=localhost,{port};Database=TestDb;User Id=sa;Password=Your_password123;TrustServerCertificate=true";

        _connection = new SqlConnection(connectionString);
        await _connection.OpenAsync();

        // Run migrations
        await RunMigrationsAsync(_connection);
    }

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

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

        var repo = new OrderRepository(_connection);

        // Act: Execute against real database
        var order = await repo.GetOrderAsync(1);

        // Assert: Verify actual database behavior
        Assert.NotNull(order);
        Assert.Equal(1, order.Id);
        Assert.Equal("CUST1", order.CustomerId);
        Assert.Equal(100.00m, order.Total);
    }
}

所需的 NuGet 包包括:

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

  <!-- Database-specific packages -->
  <PackageReference Include="Microsoft.Data.SqlClient" Version="*" />
  <PackageReference Include="Npgsql" Version="*" /> <!-- For PostgreSQL -->
  <PackageReference Include="MySqlConnector" Version="*" /> <!-- For MySQL -->

  <!-- Other infrastructure -->
  <PackageReference Include="StackExchange.Redis" Version="*" /> <!-- For Redis -->
  <PackageReference Include="RabbitMQ.Client" Version="*" /> <!-- For RabbitMQ -->
</ItemGroup>

Testcontainers 库提供了简单的 API 来管理测试中的 Docker 容器。每个测试可以启动所需的基础设施,Testcontainers 自动处理生命周期。

参考文档包括:数据库容器(SQL Server、PostgreSQL、MySQL 及迁移模式)、消息代理容器(RabbitMQ、Kafka 和 Service Bus 模式)、高级模式(网络、卷、等待策略、清理和性能优化)。

最佳实践

  • 始终使用 IAsyncLifetime 进行正确的异步设置和清理。
  • 等待端口可用:使用 WaitStrategy 确保容器就绪。
  • 使用随机端口:让 Testcontainers 自动分配端口。
  • 在测试之间清理数据:使用全新容器或截断表。
  • 尽可能复用容器:比每个测试都创建新容器更快。
  • 测试真实查询:不要只测试 Mock,要验证实际的 SQL 行为。
  • 验证约束:测试外键、唯一约束、索引。
  • 测试事务:验证回滚和提交行为。
  • 使用真实数据:用接近生产的数据量进行测试。
  • 处理清理:始终在 DisposeAsync 中释放容器。

适用场景

该技能适用于需要真实基础设施的集成测试场景,包括数据访问层测试、消息队列集成验证、Redis 缓存行为测试、数据库迁移和架构变更测试,以及希望避免对基础设施组件使用模拟对象的开发团队。

限制

资料未提供关于性能基准、收费模式、价格、具体平台兼容性细节或效果保证的信息。