用途
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 缓存行为测试、数据库迁移和架构变更测试,以及希望避免对基础设施组件使用模拟对象的开发团队。
限制
资料未提供关于性能基准、收费模式、价格、具体平台兼容性细节或效果保证的信息。