xunit测试项目需同时满足三条件:引用microsoft.net.test.sdk、测试类与方法均为public、使用[fact]/[theory]特性;断言应按语义选用assert.equal或assert.true;数据驱动测试须严格匹配参数顺序并覆盖边界值;异步测试必须返回task且用await。

能跑 dotnet test 不代表测试写对了——xUnit 项目结构、断言语义、数据驱动写法,三者错一个就白测。
怎么让 xUnit 测试项目真正跑起来
不是装了 xunit 和 xunit.runner.visualstudio 就完事。最常卡在三个地方:
-
Microsoft.NET.Test.Sdk缺失:必须显式引用,否则dotnet test报 “No test is available” - 测试类或方法不是
public:xUnit 要求两者都 public,internal class MyTests或private void TestAdd()都会被忽略 - 没用
[Fact]或[Theory]:写成[Test](NUnit)或[TestMethod](MSTest)会静默跳过
推荐一步到位创建:dotnet new xunit -n MyApp.Tests,再用 dotnet add reference ../MyApp/MyApp.csproj 加引用。
Assert.Equal vs Assert.True:别用反了
它们不是“都能断言相等”的替代品,语义和失败提示完全不同:
-
Assert.Equal(5, result):专为值比较设计,失败时明确提示Expected: 5, Actual: 4 -
Assert.True(result == 5):只判布尔结果,一旦result是null或类型不匹配,直接抛NullReferenceException,而不是断言失败 - 对返回
bool的方法(如IsEmailValid()),用Assert.True(_validator.IsEmailValid("a@b.c"))更直白;但别写成Assert.Equal(true, ...)——冗余且掩盖意图
[Theory] + [InlineData] 怎么写才不漏测
比起写十个 [Fact],[Theory] 是防漏测的关键,但容易踩两个坑:
- 参数顺序必须严格匹配方法签名:
[InlineData(2, 3, 5)]对应void Add_ReturnsCorrectSum(int a, int b, int expected),错一位就编译报错 - 不能带命名参数(不像 NUnit 的
[TestCase(a: 2, b: 3, Expected = 5)]),所以预期值必须显式传入,并在方法体内用Assert.Equal(expected, calc.Add(a, b)) - 边界值必须手动覆盖:比如测试
int.MaxValue溢出,得加一行[InlineData(int.MaxValue, 1, 0)]并配相应断言,不然永远测不到
异步测试必须返回 Task,且不能 .Wait()
xUnit 不支持 async void,也不接受同步阻塞调用:
- 正确写法:
[Fact] public async Task LoadDataAsync_ReturnsItems() { var items = await _loader.LoadAsync(); Assert.NotEmpty(items); } - 错误写法:
Assert.True(_loader.LoadAsync().Result.Count > 0)—— 可能死锁,尤其在 UI 或 ASP.NET 同步上下文中 - 如果被测方法返回
ValueTask,仍要用await,不要试图转成Task再Wait()
真正容易被忽略的,是测试中那些看似无害的静态状态——比如在 [Theory] 的多个数据组之间复用了同一个 HttpClient 实例,或共享了未清空的缓存字典。xUnit 默认每个测试方法独享新实例,但你不主动销毁资源,它就真不销毁。










