选xUnit还是NUnit取决于团队习惯和项目约束:xUnit语义更严格、天然防状态污染,适合新项目;NUnit支持SetUp/TearDown、迁移成本低,适合遗留系统。

直接用 dotnet new xunit 或 dotnet new nunit 创建测试项目,比手动配置 NuGet 包快得多,也更少出错。
创建测试项目时该选 xUnit 还是 NUnit?
选哪个取决于你团队已有的习惯和项目约束,不是语法优劣问题。xUnit 默认不支持 [SetUp]/[TearDown],NUnit 支持;但 xUnit 的 [Fact] 和 [Theory] 语义更严格,能天然规避“测试间状态污染”。如果你的项目已有大量 Assert.That 风格断言,迁移到 NUnit 成本更低;如果从零开始且用 .NET 6+,xUnit 的泛型约束支持(如 Assert.IsType)更顺手。
- xUnit 要求测试类必须是 public,且不能有构造函数参数(除非用
IEnumerable+[MemberData]) - NUnit 允许私有测试类,也支持
[OneTimeSetUp]做一次性初始化,适合需加载大配置或启动 mock server 的场景 - Visual Studio 内置对两者的测试发现都支持,但 Live Unit Testing 只原生支持 MSTest 和 xUnit(NUnit 需额外装
NUnit3TestAdapter)
测试方法命名和结构怎么写才不容易翻车?
别用 TestAdd 这种名字,它只说明“测了什么”,没说明“在什么条件下、期望什么结果”。推荐用 MethodName_StateUnderTest_ExpectedBehavior 格式,比如 Add_TwoPositiveNumbers_ReturnsSum。方法体必须严格遵循 AAA(Arrange-Act-Assert)三段,尤其注意 Act 部分只能有一行调用被测方法——多行就可能把 Arrange 或 Assert 的逻辑混进去,导致失败时定位困难。
- Arrange 阶段不要做任何 I/O、网络调用或时间依赖操作;用
DateTimeOffset.UtcNow代替DateTime.Now,避免时区/夏令时干扰 - Assert 阶段优先用具体断言,比如
Assert.Equal(expected, actual),而不是Assert.True(actual == expected);前者失败时能打印 diff,后者只报True was false - 异步测试必须标记
async Task,且用await调用被测方法;写成async void会导致测试框架无法等待完成,直接判为通过
NUnit 的 [TestCase] 和 xUnit 的 [Theory] 差在哪?
表面看都是参数化测试,但行为差异影响调试效率。NUnit 的 [TestCase(1,2,3)] 每组参数生成独立测试项,VS 测试资源管理器里能看到三个条目;xUnit 的 [Theory] + [InlineData] 也是类似效果,但若其中一组失败,xUnit 默认会中断整个理论测试,而 NUnit 会继续跑完所有用例——这对排查数据边界问题很关键。
- NUnit 支持
[TestCaseSource]从外部集合读取参数,适合需要动态生成测试数据(如读 CSV 文件)的场景 - xUnit 的
[MemberData]必须返回IEnumerable,且静态方法不能带参数;NUnit 的[TestCaseSource]方法可以是实例方法,也支持传参 - 两者都不建议在参数中传复杂对象(如
new List),因为 VS 测试窗口无法渲染,失败时只显示{1,2} System.Collections.Generic.List`1[System.Int32]
为什么测试总在 CI 上过不了,本地却没问题?
大概率是用了静态状态、时间敏感断言或文件路径硬编码。比如 DateTime.Now.ToString("HH:mm") == "14:30" 在本地碰巧对,在 CI 服务器上必然失败;又比如在测试里写 File.ReadAllText("config.json"),没指定绝对路径或没把文件设为 Copy to Output Directory,CI 构建时就读不到。
- 所有 IO 操作必须显式提供路径:用
Path.Combine(TestContext.CurrentContext.TestDirectory, "test-data.json")(NUnit)或AppContext.BaseDirectory(xUnit) - 避免
Thread.Sleep,改用Task.Delay+await,并确保超时时间足够宽松(CI 机器性能波动大) - 静态字段(如
private static int _counter)在测试间不重置,xUnit 默认每个测试类新建实例,但 NUnit 默认复用实例——这点常被忽略,导致测试顺序敏感
最易被忽略的是测试项目的 Target Framework。如果你主项目是 net6.0,测试项目却用了 netstandard2.0,某些 API(如 JsonSerializer)行为会不一致,断言看似通过,实际逻辑已偏移。










