其他
编写或迁移 .NET xUnit 测试时用:掌握 v3…
适用于已搭建并引用 xUnit 的 .NET 8.0+ 测试项目,编写、配置或从 v2 迁移 xUnit v3 测试并提升隔离性、并行安全与可诊断性。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 38/100 ×0.15
适用场景
编写或迁移 .NET xUnit 测试时特定
解决思路
- 按 xUnit v3 与 v2 差异选择包和 API:v3 使用 xunit.v3;v2 使用 xunit (2.x);v3 runner 使用 xunit.runner.visualstudio (3.x);v3 断言拆分为 xunit.v3.assert,扩展时用 xunit.v3.assert.source。
- 迁移到 v3 时:将 xunit 包替换为 xunit.v3;多数 [Fact]/[Theory] 测试无需修改;重点处理 IAsyncLifetime 返回类型从 Task 改为 ValueTask、[ClassData] 从 IEnumerable<object[]> 改为 IEnumerable<TheoryDataRow<T>>、移除 Assert 方法上的可选消息参数(v3.0 起改用自定义断言,显式消息用 Assert.Fail())。
- 用 [Fact] 编写无参数测试,例如单个行为或异常断言;用 Assert.Throws<T> 验证异常及 ParamName。
- 用 [Theory] 编写参数化测试,使同一测试逻辑用不同输入运行。
- 简单值类型参数用 [InlineData] 直接在特性中列出;例如 [InlineData(100, 10, 90)]。
- 复杂数据或共享数据集用 [MemberData] 配合 TheoryData<T>:定义静态属性返回 new TheoryData<T...>,在 [Theory] 上用 [MemberData(nameof(...))] 引用。
- 跨多个测试类共享数据用 [ClassData]:v3 数据类实现 IEnumerable<TheoryDataRow<T...>> 并 yield return new(...); v2 兼容实现 IEnumerable<object[]> 并 yield return new object[] { ... }。
- 使用 Timeout 属性:可在 [Fact] 和 [Theory] 上设置 Timeout;v2 与 v3 行为不变。
- 使用测试输出:v2 与 v3 都用 ITestOutputHelper。
- MemberData v3 支持 TheoryData<T> 和 TheoryDataRow<T>;ClassData v3 返回 IEnumerable<TheoryDataRow<T>>(强类型),v2 返回 IEnumerable<object[]>。
- 用 IClassFixture<T> 在同一测试类内共享昂贵资源(如数据库连接、配置):fixture 类 public,实现 IAsyncLifetime,v3 的 InitializeAsync/DisposeAsync 返回 ValueTask(v2 返回 Task),测试类构造函数注入 fixture。
- 用 ICollectionFixture<T> 跨测试类共享昂贵资源:定义 [CollectionDefinition("Database")] 标记类并实现 ICollectionFixture<DatabaseFixture>;在测试类上加 [Collection("Database")] 并通过构造函数注入 fixture。
- 用测试类自身实现 IAsyncLifetime 做每个测试的异步 setup/teardown,替代构造函数和 IDisposable;v3 的 InitializeAsync/DisposeAsync 返回 ValueTask。
- 理解并行默认行为:xUnit 对同一 collection 内测试类顺序执行,不同 collection 并行执行;未加 [Collection] 的测试类各自是隐式 collection,因此默认测试类并行。
- 对共享可变状态的测试禁用并行:把测试放入同一 collection,定义 [CollectionDefinition("Sequential", DisableParallelization = true)],并在测试类上加 [Collection("Sequential")]。
- 在测试项目根放置 xunit.runner.json 做程序集级并行配置:可设 parallelizeAssembly: false、parallelizeTestCollections: true、maxParallelThreads: 4;并在 csproj 用 <Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" /> 复制到输出。v2 与 v3 都支持 xunit.runner.json 且属性名相同。
- 通过构造函数注入 ITestOutputHelper 并存到实例字段,在测试中用 _output.WriteLine(...) 输出诊断信息到测试结果。
- 将 xUnit 输出桥接到 Microsoft.Extensions.Logging:实现 ILoggerProvider 和 ILogger,把 logLevel、category、formatter(state, exception) 写入 ITestOutputHelper,异常额外 WriteLine(exception.ToString());需要 Microsoft.Extensions.Logging 和写入 ITestOutputHelper 的 provider/adapter。
- 创建领域自定义断言:写静态辅助类(如 OrderAssert.HasStatus),内部用 Assert.NotNull、Assert.Single、Assert.Equal;失败时可抛 Xunit.Sdk.EqualException.ForMismatchedValues(expected, actual);在测试中调用使断言语义清晰。
- 用 Assert.Multiple(xUnit v3)分组相关断言,让一个失败后其余仍执行;v2 不支持,需分开断言且首个失败即停止。
- 在所有测试项目启用 xunit.analyzers(xUnit v3 包含)以在编译期捕获常见测试编写错误。
- 遵循 analyzer 规则:xUnit1004 测试方法不应跳过(Info);xUnit1012 值类型参数不应使用 null(Warning);xUnit1025 InlineData 应在 Theory 内唯一(Warning);xUnit2000 常量和字面量应作为 expected 参数(Warning);xUnit2002 不要对值类型做 null 检查(Warning);xUnit2007 不要用 typeof 表达式检查类型(Warning);xUnit2013 不要用相等检查判断集合大小(Warning);xUnit2017 不要用 Contains() 检查值是否存在于集合(Warning)。
- 在 .editorconfig 针对测试项目抑制规则:例如 [tests/**.cs] 下设置 dotnet_diagnostic.xUnit1004.severity = suggestion 以允许开发期间跳过测试。
- 遵循原则:每个 [Fact] 一个事实,每个 [Theory] 一个概念;如果 [Theory] 测试根本不同场景,拆成独立 [Fact]。
- 遵循原则:单类内昂贵共享资源用 IClassFixture;多类共享同一资源用 ICollectionFixture。
- 遵循原则:不要全局禁用并行;把共享可变状态的测试分组到命名 collection。
- 遵循原则:异步 setup/teardown 用 IAsyncLifetime,不用构造函数和 IDisposable(构造函数不能 async,IDisposable.Dispose 不会 await)。
- 遵循原则:测试数据靠近测试;简单情况用 [InlineData],仅当数据复杂或共享时用 [MemberData] 或 [ClassData]。
- 遵循原则:在所有测试项目启用 xUnit analyzers,捕获导致误通过或 flaky 的常见错误。
- 避免陷阱:不要在静态方法中使用构造函数注入的 ITestOutputHelper;它是每个测试实例的,应存实例字段。
- 避免陷阱:fixture 类必须 public 且有 public 无参构造函数(或 IAsyncLifetime);非 public 会静默失败。
- 避免陷阱:不要在同一方法上同时混用 [Fact] 和 [Theory]。
- 避免陷阱:异步测试方法不要返回 void;返回 Task 或 ValueTask;async void 会因 xUnit 无法观察异步完成而报告假成功。
- 避免陷阱:不要使用没有匹配 [CollectionDefinition] 的 [Collection];未匹配名称会静默创建默认行为隐式 collection,失去分组目的。
适用边界 · 注意事项
- 尚未搭建测试项目或未引用 xUnit 包时,先用 dotnet-add-testing 完成脚手架与包引用;本经验不负责创建 xUnit 项目、包引用和 Directory.Build.props。
- 需要决定测试策略/测试类型时,使用 dotnet-testing-strategy;需要 WebApplicationFactory、Testcontainers 等集成测试模式时,使用 dotnet-integration-testing。
- 项目不是 .NET 8.0+ 基线或不能使用 xUnit v3 时,需按 xUnit v2 兼容方式调整,或先用 dotnet-version-detection 确认版本。