关于 testcontainers
面向需要在集成测试中验证真实基础设施的开发者,介绍使用 Testcontainers 管理容器的测试实践。内容涵盖隔离、生命周期清理、随机端口及数据库和消息代理测试。
主要功能
- 01在容器中运行真实基础设施
- 02隔离测试数据与环境
- 03自动管理容器生命周期
- 04随机分配端口
- 05覆盖数据库和消息代理测试
适用场景
- 01针对真实数据库测试数据访问层
- 02验证消息队列集成
- 03测试数据库迁移和约束
- 04在 Docker CI 环境运行集成测试
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill testcontainers安装后在 Claude Code 中输入/testcontainers确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
reference/advanced-patterns.mddatabase-containers.mdmessage-broker-containers.md详细介绍
使用 TestContainers 进行集成测试
何时使用此技能
适用于以下场景:
- 编写需要真实基础设施(数据库、缓存、消息队列)的集成测试
- 针对实际数据库测试数据访问层
- 验证消息队列集成
- 测试 Redis 缓存行为
- 避免模拟基础设施组件
- 确保测试在类生产环境中运行
- 测试数据库迁移和架构变更
核心原则
- 使用真实基础设施,而非模拟对象——在容器中使用实际数据库和服务,而不是模拟对象
- 测试隔离——每项测试都使用全新的容器或全新的数据
- 自动清理——TestContainers 负责容器生命周期管理和清理
- 快速启动——适当时,在同一个类的测试之间复用容器
- 兼容 CI/CD——可在启用了 Docker 的 CI 环境中无缝运行
- 端口随机化——容器使用随机端口以避免冲突
为什么选择 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 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);
}
}
优势:
- 测试真实的 SQL 查询和数据库行为
- 发现约束违规、索引问题和性能问题
- 验证迁移能否正常运行
- 真正增强对数据访问层的信心
所需的 NuGet 包
<ItemGroup>
<PackageReference Include="Testcontainers" Version="*" />
<PackageReference Include="xunit" Version="*" />
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="*" />
<PackageReference Include="Npgsql" Version="*" />
<PackageReference Include="MySqlConnector" Version="*" />
<PackageReference Include="StackExchange.Redis" Version="*" />
<PackageReference Include="RabbitMQ.Client" Version="*" />
</ItemGroup>
入门
Testcontainers 库提供了一个简单的 API,可用于在测试中管理 Docker 容器。每项测试都可以启动所需的基础设施,Testcontainers 会自动处理其生命周期。
参考文档
如需了解详细模式和示例,请参阅参考文件:
- 数据库容器——SQL Server、PostgreSQL、MySQL 和迁移模式
- 消息代理容器——RabbitMQ、Kafka 和 Service Bus 模式
- 高级模式——网络、卷、等待策略、清理和性能优化
最佳实践
- 始终使用 IAsyncLifetime——正确执行异步设置和清理
- 等待端口可用——使用
WaitStrategy确保容器已就绪 - 使用随机端口——让 TestContainers 自动分配端口
- 在测试之间清理数据——使用全新的容器或清空表
- 尽可能复用容器——比每项测试都创建新容器更快
- 测试真实查询——不要只测试模拟对象;验证实际的 SQL 行为
- 验证约束——测试外键、唯一约束和索引
- 测试事务——验证回滚和提交行为
- 使用真实的数据——使用接近生产环境的数据量进行测试
- 处理清理工作——始终在
DisposeAsync中释放容器
评论
支持 Markdown