使用TestContainers和Xunit时,多项目测试如何复用单个Docker容器
多项目并行测试复用Cassandra容器的解决方案
问题根源
dotnet test默认会为每个测试项目启动独立的进程,你之前实现的项目内静态单例无法跨进程共享,所以每个项目进程都会创建自己的Cassandra容器,导致资源过载。
方案1:利用TestContainers容器复用特性(推荐)
TestContainers原生支持容器复用,开启后同一个镜像的容器会被所有测试进程共享,直到手动销毁。
实现步骤
- 封装共享容器类,开启复用并配置固定标识:
public class SharedCassandraContainer : IAsyncLifetime { private readonly CassandraContainer _container; public ISession Session { get; private set; } // 静态单例保证项目内唯一 private static readonly object _lock = new object(); private static SharedCassandraContainer _instance; private SharedCassandraContainer() { _container = new CassandraContainer("cassandra:latest") .WithReuse(true) // 开启容器复用 .WithName("shared-test-cassandra") // 固定容器名称,跨进程识别 .WithPortBinding(9042, 9042); // 固定端口映射,避免随机端口问题 } public static SharedCassandraContainer Instance { get { lock (_lock) { _instance ??= new SharedCassandraContainer(); return _instance; } } } public async Task InitializeAsync() { await _container.StartAsync(); // 初始化Cassandra会话 var cluster = Cluster.Builder() .AddContactPoint(_container.Hostname) .WithPort(_container.GetMappedPublicPort(9042)) .Build(); Session = await cluster.ConnectAsync(); } public async Task DisposeAsync() { Session?.Dispose(); // 复用模式下不要在这里销毁容器,否则其他项目无法复用 // 测试完成后手动执行:`docker rm -f shared-test-cassandra` 清理 } }
- 在各测试项目的测试类中注入使用:
public class OrderTests : IClassFixture<SharedCassandraContainer> { private readonly ISession _cassandraSession; public OrderTests(SharedCassandraContainer container) { _cassandraSession = container.Session; } // 测试方法示例 [Fact] public async Task CanInsertOrder() { // 使用_cassandraSession执行测试逻辑 } }
注意事项
- 固定端口需确保本地无其他进程占用9042,否则启动失败
- 复用模式下容器不会自动销毁,需手动清理或在CI流程中添加清理步骤
方案2:跨进程文件锁+共享连接信息
通过文件锁保证只有第一个测试进程启动容器,其他进程读取共享文件获取连接参数。
核心代码示例
public class SharedCassandraContainer : IAsyncLifetime { public ISession Session { get; private set; } private readonly string _lockFilePath = Path.Combine(Path.GetTempPath(), "test-cassandra.lock"); private readonly string _connInfoPath = Path.Combine(Path.GetTempPath(), "test-cassandra-conn.json"); private CassandraContainer _container; public async Task InitializeAsync() { // 尝试获取跨进程锁 using var lockStream = new FileStream(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); try { lockStream.Lock(0, 0); // 只有拿到锁的进程启动容器 _container = new CassandraContainer("cassandra:latest") .WithPortBinding(9042, 9042); await _container.StartAsync(); // 写入连接信息到共享文件 var connInfo = new { Host = _container.Hostname, Port = _container.GetMappedPublicPort(9042) }; File.WriteAllText(_connInfoPath, JsonSerializer.Serialize(connInfo)); } catch (IOException) { // 未拿到锁,等待连接信息文件生成 while (!File.Exists(_connInfoPath)) { await Task.Delay(1000); } } finally { lockStream.Unlock(0, 0); } // 读取连接信息并创建会话 var connData = JsonSerializer.Deserialize<dynamic>(File.ReadAllText(_connInfoPath)); var cluster = Cluster.Builder() .AddContactPoint(connData.Host) .WithPort((int)connData.Port) .Build(); Session = await cluster.ConnectAsync(); } public async Task DisposeAsync() { Session?.Dispose(); // 只有启动容器的进程负责销毁 if (_container != null) { await _container.StopAsync(); File.Delete(_connInfoPath); File.Delete(_lockFilePath); } } }
方案3:禁用测试项目并行执行
如果可以接受测试串行执行,在解决方案根目录的Directory.Build.props中添加配置:
<Project> <PropertyGroup> <VSTestParallelize>false</VSTestParallelize> </PropertyGroup> </Project>
这样dotnet test会逐个运行测试项目,项目内的单例就能生效,不会创建多个容器,但测试执行时间会变长。
内容的提问来源于stack exchange,提问作者Kalpani
相关产品推荐
相关产品推荐

