mcpField
M
migrate-nunit-to-mstest

migrate-nunit-to-mstest

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

关于 migrate-nunit-to-mstest

用于将 NUnit 3 或 4 测试项目迁移到 MSTest v4 的技能,保留目标框架和测试运行器,并指导检查、语义映射与验证。

主要功能

  1. 01检测 NUnit 版本和测试运行器
  2. 02指导替换程序包并转换测试
  3. 03映射生命周期和断言语义
  4. 04保留数据、筛选与并发行为
  5. 05规定构建和测试验证步骤

适用场景

  1. 01将 NUnit 3 或 4 项目迁移到 MSTest
  2. 02处理特定的 NUnit 到 MSTest 编译问题
  3. 03转换前检查高风险测试语义

安装

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

$npx skills add https://github.com/dotnet/skills --skill migrate-nunit-to-mstest

安装后在 Claude Code 中输入/migrate-nunit-to-mstest确认已加载。

附带文件references/mapping-cheatsheet.md

详细介绍

NUnit → MSTest 迁移

将 NUnit 3 或 4 测试转换为 MSTest v4,不更改目标框架或测试平台。成功迁移后,项目应能构建、发现相同的测试用例,并保留通过/失败、生命周期、数据、筛选和并发语义。

范围

仅当项目包含 NUnit 包或源码,且用户希望改用 MSTest 时,才使用此技能。如果项目已使用 MSTest 且不包含 NUnit 测试,应报告无需迁移测试框架,并且不做任何更改。

不要将此框架转换与目标框架升级或 VSTest/MTP 迁移合并进行。完成并验证一项迁移后,再开始另一项。

工作区约定

  • 在当前工作目录中搜索项目和源码;绝不在此技能目录下查找用户文件。
  • 判断交付要求:“转换此项目”表示编辑、构建并测试;“给我一个计划”表示只作答,不编辑。
  • 保留用户要求的项目范围。仅当共享 props 文件负责该范围内的 NUnit 包或运行器配置时,才可更改它。
  • 最终回复必须说明源 NUnit 版本、保留的运行器、更改的文件、高风险语义映射以及实际测试数量。

响应模式

  • **完整迁移请求:**检查、编辑、构建并运行测试。不要止步于计划。
  • **针对性的编译错误或 API 问题:**仅应用相关映射。
  • **不支持的目标框架:**更改包之前先停止。MSTest v4 的测试应用要求使用 .NET 8+ 或 .NET Framework 4.6.2+;可提出单独审批的 TFM 升级方案或 MSTest v3 方案。

会改变结果的决策

检测到的状态必须采取的操作
不再有 NUnit 包、命名空间、特性或约束停止,不做任何更改,并运行一次现有测试命令,以确认项目已使用 MSTest 且状态正常。
源项目使用 VSTest保留 VSTest。若仓库负责显式固定 Microsoft.NET.Test.Sdk 版本,则保留并更新该版本;不要引入 MTP 属性。
源项目使用 MTP将 NUnit 专用的 MTP 配置替换为 MSTest MTP 配置。优先使用 MSTest.Sdk;使用元包时,设置 EnableMSTestRunner=true 和 OutputType=Exe。
NUnit 使用默认的 fixture 生命周期NUnit 通常会在所有测试用例间共享同一个 fixture 实例;MSTest 则为每个测试创建新的类实例。将必须保持类级共享的状态移至静态字段,并通过 [ClassInitialize] 初始化;或者确认状态是每个测试独有的,再将其保留为实例字段。
NUnit 未配置 Parallelizable保留串行执行。不要添加 [assembly: Parallelize];NUnit 和 MSTest 默认都按串行方式执行。
NUnit 显式启用了并行执行转换其实际生效的范围和工作线程数。绝不要根据 fixture 级设置推断方法级并行。

如需查看详细映射,请在 references/mapping-cheatsheet.md 中搜索项目所用的结构,并且只阅读相关部分。

快速路径

常规迁移分四个阶段完成:一次批量发现、一次编辑、一次 dotnet test 和一份简洁结果。

  • 如有现成的 CI/测试结果,请将其作为基线。
  • 如果没有测试数量,且项目包含参数化测试、fixture 状态、命名空间设置、重试、自定义特性或显式并行配置,请在编辑前运行基线测试。
  • 若 dotnet test 已足够,不要分别运行 restore、build 和 test 命令。
  • 在行为映射完成之前,不要删除 NUnit 运行器配置。

工作流

1. 确定基线
  1. 批量读取测试项目、中央包文件、global.json、.runsettings、testconfig.json 和 NUnit 配置。
  2. 根据 NUnit、NUnit3TestAdapter、NUnit.Analyzers、NUnit.Framework、NUnit.Framework.Legacy 和 using NUnit.Framework 检测 NUnit。
  3. 根据解析出的包版本,说明源项目使用的是 NUnit 3 还是 4。
  4. 检测 VSTest 或 MTP,并予以保留。仅在无法确定时使用 platform-detection。
  5. 记录目标框架;如果 MSTest v4 不支持这些框架,则停止。
  6. 清点高风险构造:
    • [TestFixture(...)]、[TestFixtureSource]、[FixtureLifeCycle]、带参数的构造函数
    • [TestCase]、[TestCaseSource]、TestCaseData、[Values]、[Range]、[Random]、[Sequential]、[Pairwise]、自定义数据特性
    • [Theory]、[Datapoint]、[DatapointSource]、自动 bool/enum 数据点、Assume.That
    • [OneTimeSetUp]、[OneTimeTearDown]、[SetUpFixture]、基于继承的设置
    • Assert.That、Assert.Multiple、Assert.Throws、Assert.Catch、集合约束
    • [Parallelizable]、[NonParallelizable]、[LevelOfParallelism]、[Order]、[SingleThreaded]
    • [Apartment]、[RequiresThread]、[CancelAfter]、[Timeout]
    • [Culture]、[Platform]、[SetCulture]、[SetUICulture]
    • [Explicit]、[Repeat]、[Retry]、[MaxTime]、类别、属性、[TestOf]
    • [DefaultFloatingPointTolerance]、[NonTestAssembly] 和已弃用的 fixture 生命周期别名
2. 替换包,但不切换运行程序

移除要替换的 NUnit 专用包,包括 NUnit、NUnit3TestAdapter、NUnit.Analyzers、NUnit 控制台运行程序包和 NUnit 专用 MTP 适配器。

默认使用从已配置包源解析的当前稳定版 MSTest v4 元包:

<PackageReference Include="MSTest" Version="4.4.0" />

此处固定的版本仅供说明当前发行版;实际迁移时,请解析并固定当前稳定版本。如果源项目自行声明了 Microsoft.NET.Test.Sdk 依赖项,请予以保留。保留 MTP 时,优先使用 MSTest.Sdk;否则使用 EnableMSTestRunner=true 和 OutputType=Exe。

不要更改 TargetFramework。只有在转换相关行为后,才能移除 NUnit .runsettings 适配器设置。

3. 执行机械转换
NUnitMSTest
[TestFixture] 或包含测试的 fixture[TestClass]
[Test][TestMethod]
[TestCase(...)][TestMethod] + [DataRow(...)]
[TestCaseSource(nameof(Cases))][TestMethod] + [DynamicData(nameof(Cases))]
[SetUp] / [TearDown][TestInitialize] / [TestCleanup]
[OneTimeSetUp] / [OneTimeTearDown]static [ClassInitialize] / [ClassCleanup]
[Category(value)][TestCategory(value)]
[Property(key, value)][TestProperty(key, value)]
[Ignore("reason")][Ignore("reason")] 加 [TestMethod]
[Timeout(ms)][Timeout(ms)] 加 [TestMethod]
[Retry(n)]当 n > 1 时使用 [Retry(n - 1)] 加 [TestMethod];当 n = 1 时移除

移除 using NUnit.Framework; 和 using NUnit.Framework.Legacy;。使用元包时,添加 using Microsoft.VisualStudio.TestTools.UnitTesting;;MSTest.Sdk 会提供隐式全局 using。

不要机械地将类设为 sealed,也不要扁平化继承的设置方法。

4. 解析语义映射

为步骤 1 中发现的每个高风险结构加载映射速查表。这些规则必须遵守:

  • NUnit 的默认 LifeCycle.SingleInstance 与 MSTest 每个测试使用独立类实例的行为不同。必须显式保留类共享字段。
  • [OneTimeSetUp] 和 [OneTimeTearDown] 应转换为静态 MSTest 方法。将它们使用的任何实例状态移到静态持有者中,而不是仅仅添加 static。
  • NUnit 的 Assert.Throws<T> 和 Throws.TypeOf<T> 要求异常类型完全匹配,应映射为 Assert.ThrowsExactly<T>。NUnit 的 Assert.Catch<T> 和 Throws.InstanceOf<T> 允许派生类型,应映射为 Assert.Throws<T>。
  • NUnit 的 Is.TypeOf<T> 应映射为 Assert.IsExactInstanceOfType<T>;Is.InstanceOf<T> 应映射为 Assert.IsInstanceOfType<T>。
  • NUnit 的相等约束会逐项比较序列。应在 MSTest 4.3 及更高版本中使用 Assert.AreSequenceEqual,或使用已物化的列表调用 CollectionAssert.AreEqual;切勿对序列使用基于引用的 Assert.AreEqual。
  • TestCase(ExpectedResult=...) 和 TestCaseData.Returns(...) 需要重写目标测试,以断言预期结果。
  • 在需要时,通过从 DynamicData 返回 TestDataRow<T>,保留每一行的名称、类别和忽略状态。NUnit 行属性没有直接对应的 TestDataRow<T>,需要自定义数据源或明确决定如何处理元数据。
  • MSTest 4.4 的组合属性可映射 NUnit 的 [Combinatorial]、[Values]、[Range] 和 [Random];请添加 using Microsoft.VisualStudio.TestTools.UnitTesting.Combinatorial;。构造函数语义有所不同,因此应明确转换 NUnit 的范围和随机边界。[Sequential] 仍需要显式行或自定义数据源。
  • NUnit 的 [Pairwise] 没有 MSTest 内置对应项。应在 DynamicData 中保留完全相同的生成行,或使用经过验证的成对 ITestDataSource;完整的笛卡尔积组合会改变测试数量和执行成本。
  • 将 [Theory]、[Datapoint] 和 [DatapointSource] 转换为显式的 DynamicData 或组合数据源。保留自动生成的 bool/enum 值,以及理论测试在所有行都违反假设时会失败这一规则。
  • [Explicit]、[Repeat]、参数化 fixture、fixture 数据源和 Assert.Multiple 都没有行为完全一致的机械映射。应谨慎重写或报告需人工跟进;切勿默默采用近似映射。
  • NUnit 的 [Retry(n)] 将初始尝试计入次数,而 MSTest 的 [Retry(n)] 统计初始尝试之后的重试次数。应减去 1,并单独审查 NUnit 的 RetryExceptions 筛选器。
  • [CancelAfter(ms)] 应映射为 [Timeout(ms, CooperativeCancellation = true)]。在 VSTest 下,移除 NUnit 注入的 CancellationToken 参数,并使用注入的或基于属性的 TestContext.CancellationToken;否则,VSTest 会将其视为缺少数据。仅当已证明保留的运行器支持注入时,才保留方法令牌。将 fixture 级默认值扩展到每个受影响的测试和生命周期方法。
  • [Apartment(ApartmentState.STA)] 应映射为 [STATestClass] 或 [STATestMethod]。如果异步延续必须保留在 STA 线程上,请设置 UseSTASynchronizationContext = true。MTA 是 MSTest 的默认值。[RequiresThread] 和 [SingleThreaded] 保证线程身份,而 MSTest 属性通常无法保留这一点;应使用自定义执行器或报告需人工跟进。
  • 将 NUnit 的平台和区域性限制映射为 OSCondition、ArchitectureCondition 或 MemberCondition 帮助程序。将 SetCulture 和 SetUICulture 映射为保存并恢复原始区域性的 setup/cleanup。
  • 将 [DefaultFloatingPointTolerance] 展开为每个受影响断言的显式 delta。保留方法优先于 fixture、fixture 优先于程序集的优先级。
5. 保留生命周期和命名空间设置
  • [SetUp] 和 [TearDown] 仍是每个测试的生命周期方法。
  • 对于 NUnit 默认的单个夹具实例,请审查所有可变实例字段。如果测试依赖共享状态,请使用 [ClassInitialize] 创建静态状态,并通过 [ClassCleanup] 释放。
  • [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] 已与 MSTest 的类实例化方式一致;移除该特性并保留每个测试的实例状态。
  • NUnit 的 [SetUpFixture] 作用于命名空间范围。MSTest 的程序集初始化则作用于整个程序集。只有当 NUnit 设置原本就覆盖整个程序集时才使用 [AssemblyInitialize];否则,将设置移至受影响的类或共享辅助程序中,不要扩大作用范围。
  • 保留设置和清理的继承顺序。除非能证明结果顺序等效,否则不要合并基类和派生类的方法。
  • 将 NUnit 的 TestContext.WriteLine 映射为通过注入或属性获取的 MSTest TestContext.WriteLine。分别转换目录、测试名称和附件相关 API。
6. 保留并行化和排序

NUnit 和 MSTest 默认都按串行方式运行。对于未配置并行化的 NUnit 项目,不要添加并行化。

当 NUnit 明确启用并行化时:

  • 程序集级别的 [Parallelizable(ParallelScope.Fixtures)] -> [assembly: Parallelize(Workers = N, Scope = ExecutionScope.ClassLevel)]
  • 夹具级别的 [Parallelizable(ParallelScope.Children)] 或方法级并行化 -> 仅在确认共享实例状态安全后,才使用 MSTest 方法级并行化
  • 启用并行化时,[NonParallelizable] -> [DoNotParallelize]
  • [LevelOfParallelism(N)] -> Workers = N
  • 如果能保留比 [DoNotParallelize] 更窄的锁定范围,可使用 MSTest 4.4 的 [ResourceLock("key")] 实现针对资源的串行化

NUnit 的 [Order] 并不表示通用的依赖关系语义。只有当源代码确实表达前置条件时,才使用 MSTest 4.4 的 [DependsOn];否则,通过让测试相互独立来移除排序,或报告为需手动跟进。

7. 验证一致性
  1. 使用与基线相同的平台、筛选器和配置运行测试。
  2. 比较发现、通过、失败和跳过的数量。
  3. 调查每一处差异:
    • 缺失的行 -> DataRow、DynamicData、组合式数据或行元数据转换
    • 异常或类型发生变化 -> 精确类型与派生类型映射
    • 状态失败 -> NUnit 单实例夹具语义或设置顺序
    • 并发失败 -> Parallelize、DoNotParallelize、工作线程数或资源锁
    • 跳过情况发生变化 -> Ignore、Explicit、行级忽略或重试行为
  4. 确认没有遗留 NUnit 包、命名空间、特性、约束、适配器设置或自定义 NUnit 扩展,除非已记录为需后续跟进。
  5. 回读高风险变更文件,并在结果中列出确切的目标 API。

使用以下最终回复格式:

  • 已更改: 文件及确切的高风险映射。
  • 已验证: 最终命令,以及发现、通过、失败和跳过的数量。
  • 已保留: 目标框架、测试平台、夹具范围和并发选择。
  • 待处理: 需手动跟进的事项,或无。

完成标准

  • 已确认 NUnit 版本和测试平台
  • 已转换 NUnit 包和源代码结构
  • 目标框架和测试平台保持不变
  • 已审查并在可观察到的范围内保留默认单实例夹具语义
  • 已保留数据行元数据和断言语义
  • 未在未说明的情况下扩大命名空间设置和并行化范围
  • 已将 NUnit 官方特性索引中的每个特性归类为直接转换、重写、移除或手动处理
  • 构建成功,测试结果与基线一致
  • 已说明不受支持的自定义扩展或行为

后续操作

如果用户也需要 MTP,请单独运行 migrate-vstest-to-mtp。只有在一致性得到确认后,才使用 writing-mstest-tests。

评论

支持 Markdown