为何选择运行单个测试时,集合中其他测试也会执行?
问题背景
我有一个包含4个测试的类,该类归属带有共享DatabaseFixture的测试集合。在Visual Studio Test Explorer中执行类内单个测试时,测试本身能通过,但查看数据库状态发现其他测试也在运行——SQL Profiler证实了这一点。
SQL Profiler还显示,选择运行单个测试时,DatabaseFixture的构造函数会被多次执行。右键代码编辑器中的单个测试选择运行,结果一致。
我尝试将该类从集合中移除,改为在每个测试方法内实例化DatabaseFixture,此时单个测试运行后的数据库状态符合预期,但我不理解为何移除集合会有这种差异。按我的理解,Collection Fixture的核心作用就是为集合内所有测试仅运行一次。
由于DatabaseFixture构造函数会加载部分数据,我更希望保留集合模式——为每个测试实例化Fixture速度慢,且每个测试方法需要大量配置工作。
简化示例代码
DatabaseFixture实现
public class DatabaseFixture : IDisposable { internal IDbConnection Db; internal IConfiguration Configuration; internal OrmLiteConnectionFactory dbFactory; public DatabaseFixture() { Configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); var connectionString = Configuration.GetConnectionString(name: "Connection"); dbFactory = new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider, false); Db = dbFactory.Open(); Db.ExecuteNonQuery("DELETE FROM TestTable"); } public void Dispose() { } }
测试集合定义
[CollectionDefinition("TestCollection")] public class TestCollection : ICollectionFixture<DatabaseFixture> { }
测试类
[Collection("TestCollection")] public class TestClass { DatabaseFixture dbFixture; public TestClass(DatabaseFixture database) { dbFixture = database; } [Fact] public void Test1() { dbFixture.Db.Insert<TestTable>(new TestTable { Value = DateTime.Now.ToString(), Method = "TEST 1" }); } [Fact] public void Test2() { dbFixture.Db.Insert<TestTable>(new TestTable { Value = DateTime.Now.ToString(), Method = "TEST 2" }); } }
现象对比
- 仅执行Test1时,表中出现两条数据(Test1和Test2的插入记录);
- 将TestClass修改为以下代码后仅执行Test1,表中仅出现一条预期数据:
public class TestClass { DatabaseFixture dbFixture; [Fact] public void Test1() { dbFixture = new(); dbFixture.Db.Insert<TestTable>(new TestTable { Value = DateTime.Now.ToString(), Method = "TEST 1" }); } [Fact] public void Test2() { dbFixture = new(); dbFixture.Db.Insert<TestTable>(new TestTable { Value = DateTime.Now.ToString(), Method = "TEST 2" }); } }
原因分析
xUnit.net的默认行为是并行执行测试集合内的测试,即使你选择运行单个测试,测试运行器可能会因为并行机制、测试发现流程等原因,实例化多个DatabaseFixture并执行同一类中的其他测试。另外,共享的数据库连接会导致不同测试的操作互相干扰——Fixture构造时仅执行一次数据清理,后续所有测试共用同一个连接,若其他测试被并行触发,就会向数据库插入额外数据。
Collection Fixture的确设计为集合内共享单实例,但并行执行会打破这个预期:测试运行器可能为不同的测试线程创建多个Fixture实例,尤其是当测试类被并行实例化时。
解决方案
1. 禁用测试并行执行
在项目根目录创建xunit.runner.json文件,添加以下配置:
{ "parallelizeTestCollections": false, "parallelizeAssembly": false }
同时在Visual Studio的Test Explorer设置中,取消勾选“并行运行测试”选项。这能确保Fixture仅被实例化一次,且测试串行执行,避免互相干扰。
2. 为每个测试提供隔离的数据库连接
修改DatabaseFixture,不再保持长期打开的连接,而是为每个测试提供新连接并重置数据:
public class DatabaseFixture : IDisposable { internal IConfiguration Configuration; internal OrmLiteConnectionFactory dbFactory; public DatabaseFixture() { Configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); var connectionString = Configuration.GetConnectionString(name: "Connection"); dbFactory = new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider, false); } // 为每个测试获取新连接并清理数据 public IDbConnection GetDbConnection() { var db = dbFactory.Open(); db.ExecuteNonQuery("DELETE FROM TestTable"); return db; } public void Dispose() { // 清理连接工厂资源 } }
修改测试类,每个测试实例使用独立连接:
[Collection("TestCollection")] public class TestClass : IDisposable { DatabaseFixture dbFixture; IDbConnection db; public TestClass(DatabaseFixture database) { dbFixture = database; db = dbFixture.GetDbConnection(); } [Fact] public void Test1() { db.Insert<TestTable>(new TestTable { Value = DateTime.Now.ToString(), Method = "TEST 1" }); } [Fact] public void Test2() { db.Insert<TestTable>(new TestTable { Value = DateTime.Now.ToString(), Method = "TEST 2" }); } public void Dispose() { db.Dispose(); } }
这种方式既保留了Collection Fixture的共享配置优势,又保证了每个测试的数据隔离,即使并行执行也不会出现数据干扰。
内容的提问来源于stack exchange,提问作者Jmeijer

