mcpField
D
dotnet-test-frameworks

dotnet-test-frameworks

github.com/dotnet测试与质量5.8k460 次安装1 天前更新
代码仓库

关于 dotnet-test-frameworks

供 .NET 开发者和代码审查工具识别测试并遵循框架惯例的参考资料,涵盖 MSTest、xUnit、NUnit 和 TUnit,包括断言、生命周期钩子及集成测试标记。

主要功能

  1. 01识别测试类和测试方法
  2. 02对照四种框架的断言 API
  3. 03识别跳过和忽略标记
  4. 04识别集成测试及外部依赖
  5. 05查阅初始化与清理约定

适用场景

  1. 01分类 .NET 代码库中的测试
  2. 02建议框架原生的异常断言
  3. 03检查测试中的外部依赖
  4. 04识别测试生命周期和跳过行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dotnet/skills --skill dotnet-test-frameworks

安装后在 Claude Code 中输入/dotnet-test-frameworks确认已加载。

详细介绍

.NET 测试框架参考

.NET 测试框架(MSTest、xUnit、NUnit、TUnit)的语言专属检测模式。

测试文件识别

框架测试类标记测试方法标记
MSTest[TestClass][TestMethod]、[DataTestMethod]
xUnit(无——基于约定)[Fact]、[Theory]
NUnit[TestFixture][Test]、[TestCase]、[TestCaseSource]
TUnit(无——基于约定)[Test]

各框架的断言 API

类别MSTestxUnitNUnitTUnit
相等Assert.AreEqualAssert.EqualAssert.That(x, Is.EqualTo(y))await Assert.That(x).IsEqualTo(y)
布尔值Assert.IsTrue / Assert.IsFalseAssert.True / Assert.FalseAssert.That(x, Is.True)await Assert.That(x).IsTrue() / await Assert.That(x).IsFalse()
空值Assert.IsNull / Assert.IsNotNullAssert.Null / Assert.NotNullAssert.That(x, Is.Null)await Assert.That(x).IsNull() / await Assert.That(x).IsNotNull()
异常Assert.Throws<T>() / Assert.ThrowsExactly<T>()Assert.Throws<T>()Assert.That(() => ..., Throws.TypeOf<T>())await Assert.That(() => ...).Throws<T>() / await Assert.That(() => ...).ThrowsExactly<T>()
集合CollectionAssert.ContainsAssert.ContainsAssert.That(col, Has.Member(x))await Assert.That(col).Contains(x)
字符串StringAssert.ContainsAssert.Contains(str, sub)Assert.That(str, Does.Contain(sub))await Assert.That(str).Contains(sub)
类型Assert.IsInstanceOfTypeAssert.IsAssignableFromAssert.That(x, Is.InstanceOf<T>())await Assert.That(x).IsAssignableTo<T>()(精确类型检查请使用 await Assert.That(x).IsTypeOf<T>())
结果不确定Assert.Inconclusive()通过 [Fact(Skip)] 跳过Assert.Inconclusive()Skip.Test("reason")(没有真正的结果不确定状态)
失败Assert.Fail()Assert.Fail()(.NET 10+)Assert.Fail()Assert.Fail()

**TUnit 专属:**断言是异步的,必须使用 await 等待——如果忘记使用 await,断言将不会运行,测试会在未提示的情况下通过。内置分析器会在缺少 await 时发出警告。可以通过 .And / .Or 链式调用组合多个断言,也可以使用 Assert.Multiple() 将它们分组。

第三方断言库:Should*(Shouldly)、.Should()(FluentAssertions / AwesomeAssertions)、Verify()(Verify)。TUnit 还提供可选的 TUnit.Assertions.Should 包,它基于相同的基础设施提供 FluentAssertions 风格的 value.Should().BeEqualTo(...)。

Sleep/Delay 模式

模式示例
线程休眠Thread.Sleep(2000)
任务延迟await Task.Delay(1000)
SpinWaitSpinWait.SpinUntil(() => condition, timeout)

跳过/忽略注解

框架注解带原因
MSTest[Ignore][Ignore("reason")]
xUnit[Fact(Skip = "reason")](必须提供原因)
NUnit[Ignore("reason")](必须提供原因)
TUnit[Skip("reason")](必须提供原因;也可用于类和程序集范围,例如 [assembly: Skip("…")]。可在测试中动态跳过:Skip.Test("reason")。)
条件#if false / #if NEVER(无法提供原因)

异常处理——惯用替代方案

如果测试使用 try/catch 验证异常,请建议使用框架原生的替代方式:

MSTest:

// Instead of try/catch (matches exact type):
var ex = Assert.ThrowsExactly<InvalidOperationException>(
    () => processor.ProcessOrder(emptyOrder));
Assert.AreEqual("Order must contain at least one item", ex.Message);

// Or (also matches derived types):
var ex = Assert.Throws<InvalidOperationException>(
    () => processor.ProcessOrder(emptyOrder));
Assert.AreEqual("Order must contain at least one item", ex.Message);

xUnit:

var ex = Assert.Throws<InvalidOperationException>(
    () => processor.ProcessOrder(emptyOrder));
Assert.Equal("Order must contain at least one item", ex.Message);

NUnit:

var ex = Assert.Throws<InvalidOperationException>(
    () => processor.ProcessOrder(emptyOrder));
Assert.That(ex.Message, Is.EqualTo("Order must contain at least one item"));

TUnit:

await Assert.That(() => processor.ProcessOrder(emptyOrder))
    .Throws<InvalidOperationException>()
    .WithMessage("Order must contain at least one item");

// Or, for exact-type matching (no derived types):
await Assert.That(() => processor.ProcessOrder(emptyOrder))
    .ThrowsExactly<InvalidOperationException>();

神秘访客 — 常见 .NET 模式

异味指标检查内容
文件系统File.ReadAllText、File.Exists、File.WriteAllBytes、Directory.GetFiles、使用硬编码路径的 Path.Combine
数据库SqlConnection、DbContext(未使用内存中提供程序)、SqlCommand
网络未重写 HttpMessageHandler 的 HttpClient、WebRequest、TcpClient
环境Environment.GetEnvironmentVariable、Environment.CurrentDirectory
可接受MemoryStream、StringReader、内存中数据库提供程序、自定义 DelegatingHandler

集成测试标记

将以下情况识别为集成测试(并相应调整异味严重程度):

  • 类名包含 Integration、E2E、EndToEnd 或 Acceptance
  • [TestCategory("Integration")](MSTest)
  • [Trait("Category", "Integration")](xUnit)
  • [Category("Integration")](NUnit、TUnit)
  • 项目名称以 .IntegrationTests 或 .E2ETests 结尾

设置/清理方法

框架设置清理
MSTest[TestInitialize] 或构造函数[TestCleanup] 或 IDisposable.Dispose / IAsyncDisposable.DisposeAsync
xUnit构造函数IDisposable.Dispose / IAsyncDisposable.DisposeAsync
NUnit[SetUp][TearDown]
TUnit[Before(Test)] 或构造函数[After(Test)] 或 IDisposable.Dispose / IAsyncDisposable.DisposeAsync
MSTest(类)[ClassInitialize][ClassCleanup]
NUnit(类)[OneTimeSetUp][OneTimeTearDown]
xUnit(类)IClassFixture<T>fixture 的 Dispose
TUnit(类)[Before(Class)][After(Class)]
TUnit(程序集)[Before(Assembly)][After(Assembly)]
TUnit(会话)[Before(TestSession)][After(TestSession)]

TUnit 专属: [BeforeEvery(Test)] / [AfterEvery(Test)](以及 Class / Assembly 变体)会在整个测试运行期间,对每个测试/类/程序集运行——适用于全局横切钩子。钩子还可以选择接收上下文对象(TestContext、ClassHookContext 等)和/或 CancellationToken。

评论

支持 Markdown