关于 testcontainers-integration-tests
testcontainers-integration-tests 是一项 .NET 集成测试技能,指导开发者使用容器中的真实基础设施,而非模拟对象。它基于 TestContainers 4.x,涵盖数据库、缓存和消息队列测试,以及隔离和清理方法。
主要功能
- 01使用容器化的真实基础设施
- 02涵盖 SQL Server、PostgreSQL、Redis 和 RabbitMQ
- 03提供隔离与容器生命周期指导
- 04包含迁移和数据库重置示例
- 05提供 CI 配置和性能建议
适用场景
- 01针对真实数据库测试数据访问
- 02验证 Redis 或 RabbitMQ 集成
- 03检查迁移、约束和事务
- 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 缓存行为
- 避免为基础设施组件使用模拟对象
- 确保测试在类似生产环境的环境中运行
- 测试数据库迁移和架构变更
参考文件
- database-patterns.md:SQL Server、PostgreSQL 和迁移测试示例
- infrastructure-patterns.md:Redis、RabbitMQ、多容器网络、容器复用和 Respawn
核心原则
- 使用真实基础设施,而非模拟对象 - 在容器中使用实际的数据库/服务,而不是模拟对象
- 测试隔离 - 每项测试都使用全新的容器或全新的数据
- 自动清理 - TestContainers 负责容器生命周期和清理
- 快速启动 - 适当时在同一类的测试之间复用容器
- 兼容 CI/CD - 在启用了 Docker 的 CI 环境中无缝运行
- 端口随机化 - 容器使用随机端口以避免冲突
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(...) |
TestcontainersContainer | DockerContainer / 模块容器(例如 PostgreSqlContainer) |
TestcontainersNetworkBuilder | NetworkBuilder |
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;
最佳实践
- 始终使用 IAsyncLifetime - 正确进行异步初始化和清理
- 等待端口可用 - 使用
Wait策略确保容器已就绪 - 使用随机端口 - 让 TestContainers 自动分配端口
- 在测试之间清理数据 - 使用全新容器或清空表
- 尽可能复用容器 - 比每次测试都创建新容器更快
- 测试真实查询 - 不要只测试模拟对象;验证实际 SQL 行为
- 验证约束 - 测试外键、唯一约束和索引
- 测试事务 - 验证回滚和提交行为
- 使用真实数据 - 使用接近生产环境数据量的数据进行测试
- 处理清理工作 - 始终在
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
性能提示
- 复用容器 - 在一个集合中的多个测试之间共享固定装置
- 使用 Respawn - 无需重新创建容器即可重置数据
- 并行执行 - TestContainers 会自动处理端口冲突
- 使用轻量级镜像 - Alpine 版本体积更小、速度更快
- 缓存镜像 - Docker 会在本地缓存已拉取的镜像