mcpField
M
migrate-xunit-to-mstest

migrate-xunit-to-mstest

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

关于 migrate-xunit-to-mstest

将 xUnit.net v2 或 v3 测试项目迁移到 MSTest v4,同时保留目标框架和测试平台的技能。重点处理语义一致性、运行器配置及构建和测试验证。

主要功能

  1. 01xUnit 属性与断言映射到 MSTest
  2. 02保留 VSTest 或 MTP 运行器
  3. 03映射夹具生命周期与并行设置
  4. 04检查目标框架兼容性
  5. 05构建与测试验证

适用场景

  1. 01将 xUnit 测试项目迁移到 MSTest
  2. 02移植夹具和测试生命周期行为
  3. 03处理断言及运行器差异

安装

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

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

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

附带文件references/mapping-cheatsheet.md

详细介绍

xUnit -> MSTest 迁移

将 xUnit.net v2 或 v3 测试转换为 MSTest v4,且不更改目标框架或测试平台。成功迁移后,项目可以构建、发现相同的测试,并保留通过/失败结果和执行语义。

范围

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

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

工作区约定

  • 激活技能后继续操作。在当前工作目录中搜索已暂存的项目和源代码;绝不要在此技能的基础目录下查找用户文件。
  • 打开 glob/搜索返回的原始路径。如果读取或补丁工具拒绝刚找到的路径,请改用其他可用工具重试。只有在穷尽当前工作区的搜索后,才能向用户询问路径。
  • 根据用户要求的交付内容进行判断:“转换此项目”表示需要编辑、构建并测试;“给我一个计划”或“我该如何转换?”表示需要作答。如果文件已存在,不要用“请提供文件”来代替实际操作。
  • 最终回复必须说明源 xUnit 版本、保留的运行器、已更改的文件、应用的每项高风险语义映射,以及实际测试数量。关于 fixture 生命周期、Owner 映射、取消或并行化的说明,必须体现在最终生成的源代码中,不能只写在说明文字里。

响应模式

  • **完整迁移请求:**检查项目、进行修改、构建并运行测试。不要只提供计划。
  • **针对特定编译错误或 API 的问题:**检查相关代码,并仅应用对应的映射。不要叙述完整工作流程。
  • **不支持的目标框架:**在更改包之前停止。MSTest v4 要求测试应用使用 .NET 8+ 或 .NET Framework 4.6.2+;可提议经单独批准升级 TFM,或将 MSTest v3 作为中间目标。

会影响结果的决策

在机械式映射之前,先执行以下决策:

检测到的状态必须采取的操作
不再存在 xUnit 包、命名空间、特性或 fixture停止操作。不更改任何文件,说明无需迁移,并运行一次现有的 dotnet test 命令,以确认当前 MSTest 项目运行正常。
源项目使用 VSTest保留现有的 VSTest 属性/配置。优先保留并更新源项目显式指定的 Microsoft.NET.Test.Sdk 版本;如果仓库有意依赖 MSTest 汇总包的传递依赖,也可以沿用此约定。不要引入 MTP 属性。
源项目使用 MTP将 xUnit 专用的 MTP 选择替换为 MSTest MTP 配置。优先使用 MSTest.Sdk;若使用汇总包,则设置 EnableMSTestRunner=true 和 OutputType=Exe。保留原生与桥接命令集成方式,不要添加 <UseVSTest>true</UseVSTest> 或其他仅适用于 VSTest 的配置。
源项目依赖 xUnit 的默认并行化如果当前项目至少有两个可独立运行的测试类,则在一个已编译的 .cs 文件中添加 [assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)]。如果项目只有一个测试类且未显式设置并行方式,则省略此项,因为类级并发无法体现。无论当前测试类数量多少,都要转换显式的 CollectionBehavior 或 xunit.runner.json 设置。报告完成前,重新读取已更改的文件,并在结果中指出其名称。

如需了解详细映射和示例,请在 references/mapping-cheatsheet.md 中搜索项目实际包含的结构,并且只阅读对应章节。不要加载或复述整份参考文档。

快速路径

对于常规项目迁移,分四个阶段完成:一次批量发现读取/搜索、一次编辑、一条 dotnet test 命令,以及一份简洁结果。不要:

  • 列出目录后,再通过其他工具重新读取相同文件
  • 将项目文件复制到已加载的技能或其 references/ 目录中;这些文件是只读指南,不是编辑工作区
  • 除非已确认还原状态是最新的,否则不要尝试 dotnet test --no-restore
  • 在 dotnet test 已足够时,分别运行还原、生成和测试命令
  • 重复运行已通过的测试命令,或检查未更改的文件进行确认

如有现成的 CI/测试结果,请将其用作一致性基准。只有在计数不可用,且迁移包含数据驱动测试、固定数据、跳过项、自定义扩展、共享状态或其他无法仅凭源代码确定一致性的行为时,才运行新的编辑前基准测试。

工作流

1. 确定基准
  1. 在一次发现过程中,批量读取测试项目以及 Directory.Build.props、Directory.Packages.props、global.json 和运行器配置,并搜索源码中以下高风险构造。
  2. 说明检测到的源版本:
    • xunit 2.x 及相关包 -> xUnit v2
    • xunit.v3 或 xunit.v3.* -> xUnit v3
  3. 根据项目和仓库配置识别 VSTest 或 MTP。仅在平台不明确时使用 platform-detection,并保留检测到的平台。
  4. 记录目标框架;如果 MSTest v4 不支持这些框架,则停止。
  5. 如果快速路径要求建立新基准,则运行一次现有测试命令,并记录发现、通过、失败和跳过的数量。
  6. 编辑前清点高风险构造:
    • IClassFixture、ICollectionFixture、CollectionDefinition、自定义 FactAttribute/TheoryAttribute/DataAttribute
    • Assert.Throws、ThrowsAny、IsType、Record.Exception、事件断言
    • ITestOutputHelper、TestContext.Current、IAsyncLifetime
    • CollectionBehavior、xunit.runner.json、共享静态状态或外部状态
2. 替换包,但不要切换运行器

从项目文件和中央包文件中移除 xUnit 包,包括 xunit*、xunit.v3.*、xunit.runner.visualstudio、YTest.MTP.XUnit2,以及将被替换的 xUnit 专用配套包。

对于增量转换,默认使用 MSTest v4 元包:


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

此元包包含 Microsoft.NET.Test.Sdk、MSTest.TestAdapter、MSTest.TestFramework 和 MSTest.Analyzers。保留 VSTest 时,优先保留源项目中显式指定的 Microsoft.NET.Test.Sdk 版本,并将其更新为与所选 MSTest 版本兼容的版本;这样更便于审查运行器和版本兼容性。有意依赖元包传递依赖项的仓库也可以保留这一惯例。对于上述示例中的版本,MSTest 4.1.0 要求 Microsoft.NET.Test.Sdk 18.0.1 或更高版本;不兼容的旧版本可能导致 NU1605。

保留 MTP 时,不要将 xUnit 的 UseMicrosoftTestingPlatformRunner 属性带入 MSTest 项目。优先使用已解析版本的 MSTest.Sdk。如果仓库惯例要求使用元包方案,则设置 <EnableMSTestRunner>true</EnableMSTestRunner> 和 <OutputType>Exe</OutputType>。只有在仓库继续通过 VSTest 命令模式调用 MTP 应用时,才保留 TestingPlatformDotnetTestSupport=true;原生 .NET 10 及更高版本的 MTP 模式不需要此设置。使用 MSTest.Sdk 保留 VSTest 时,设置 <UseVSTest>true</UseVSTest>。

不要更改 TargetFramework。只有移植了 xunit.runner.json 中相关设置后,才移除该文件。

3. 执行机械转换

先应用常见的改写:

xUnitMSTest
无类特性[TestClass]
[Fact][TestMethod]
[Theory] + [InlineData][TestMethod] + [DataRow]
[MemberData][DynamicData]
[Fact(Skip = "...")][TestMethod] + [Ignore("...")]
[Trait("Category", value)][TestCategory(value)]
[Trait("Owner", value)][Owner(value)]
其他 [Trait(key, value)][TestProperty(key, value)]
Assert.Equal / NotEqualAssert.AreEqual / AreNotEqual
Assert.True / FalseAssert.IsTrue / IsFalse
Assert.Null / NotNullAssert.IsNull / IsNotNull

移除 using Xunit; 和 using Xunit.Abstractions;。使用元包选项时,添加 using Microsoft.VisualStudio.TestTools.UnitTesting;;MSTest.Sdk 会隐式导入它。

保留现有的类继承关系。不要机械地将类设为 sealed。

4. 处理语义映射

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

  • xUnit 的 Assert.Throws<T> 要求精确类型,应映射到 MSTest 的 Assert.ThrowsExactly<T>。
  • xUnit 的 Assert.ThrowsAny<T> 允许派生类型,应映射到 MSTest 的 Assert.Throws<T>。
  • xUnit 的 Assert.IsType<T> 要求精确类型,应映射到泛型 Assert.IsExactInstanceOfType<T>;Assert.IsAssignableFrom<T> 应映射到泛型 Assert.IsInstanceOfType<T>。如果将 xUnit 断言的类型化返回值赋给变量,应保留该赋值,并使用 MSTest 的泛型重载,而非非泛型 Type 重载。
  • xUnit 的 Assert.Equal 用于序列时会比较元素。MSTest 4.3+ 应使用 Assert.AreSequenceEqual;较早的 v4 应使用 CollectionAssert.AreEqual 并传入已物化的列表;绝不能将序列相等替换为基于引用的 Assert.AreEqual。
  • [Ignore] 和 [Timeout] 是修饰特性;保留 [TestMethod],确保测试能被发现。
  • [DataRow] 的值必须与参数类型完全匹配。
  • TestContext.Current.CancellationToken 应映射为注入的 MSTest TestContext.CancellationToken;绝不能将其替换为 CancellationToken.None 或新的 CancellationTokenSource。
  • Owner 是保留的 VSTest 属性。将 [Trait("Owner", value)] 映射为 [Owner(value)],而非 [TestProperty("Owner", value)]。
  • 对于没有 MSTest 对应项的断言(Assert.Collection、Assert.All、Assert.Equivalent、Record.Exception、事件断言),必须明确手动改写。绝不能删除断言而不替换其验证逻辑。

当清单已明确所需映射时,应在一次编辑中完成机械改写和语义改写。默认不要运行中间构建;根据最终验证的编译错误,仅处理尚未解决的转换。

5. 保留生命周期、夹具作用域和并行化
  • 有效时保留构造函数设置以及 IDisposable/IAsyncDisposable。将 IAsyncLifetime 映射为 [TestInitialize]/[TestCleanup]。
  • IClassFixture<T> 表示每个测试类有一个夹具实例,供该类中的所有方法共享。将其映射为一个静态 T 字段,由接受 TestContext 的静态 [ClassInitialize] 方法创建一次,并由静态 [ClassCleanup] 一次性释放。此映射绝不可使用 [TestInitialize]/[TestCleanup],因为那会导致每个测试方法各有一个夹具。
  • 对于 ICollectionFixture<T>,同时保留共享和串行执行行为。优先使用由各成员类调用的静态 Lazy<T> 辅助程序;仅当源集合禁用了并行化时,才添加 [DoNotParallelize]。只有当夹具确实是整个程序集范围时,才使用程序集初始化。
  • 将 ITestOutputHelper 替换为通过构造函数注入或基于属性的 MSTest TestContext,并将每个 _output.WriteLine(...) 调用替换为相应的 TestContext.WriteLine(...)。最终结果必须明确写出 TestContext 的注入/属性方式以及 WriteLine 的映射;仅说“已迁移输出”不足以证明行为一致。

xUnit 默认并行运行各测试类;MSTest 则串行运行。当当前项目有两个或更多可独立运行的测试类时,使用以下方式保留这一有效行为:

[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)]

对于没有显式 xUnit 并行设置的单类项目,不要添加程序集策略:无需保留类级并发。始终翻译显式的 CollectionBehavior 或 xunit.runner.json 设置。绝不可使用 ExecutionScope.MethodLevel 来模拟 xUnit。应用夹具作用域或并行化决策前,说明源项目共享或串行执行的内容,以及目标项目如何保留这些行为。

6. 验证行为一致性
  1. 使用基线测试所用的相同平台、筛选器和配置运行一次测试。dotnet test 默认会构建;仅当需要单独定位编译失败时,才单独运行构建。
  2. 比较已发现、通过、失败和跳过的测试数量。
  3. 在宣布完成前调查每一处差异:
    • 缺失用例 -> 发现特性、DynamicData 或 DataRow 字面量类型
    • 异常行为变化 -> 精确类型与派生类型断言的映射
    • 共享状态失败或耗时大幅变化 -> 夹具作用域和并行化
    • 测试被静默跳过 -> 缺少 [TestMethod] 或运行时跳过转换不正确
  4. 确认没有残留 xUnit 包、命名空间、特性、运行器配置或夹具接口,除非已明确记录为需手动跟进的事项。
  5. 最终测试通过后,重新检查每个实现高风险映射的变更文件,以及任何运行器或并行化配置。在结果中写明文件名,以及用于实现生命周期、数据、跳过、输出、断言、夹具作用域或并行化行为的具体目标 API。保留类型参数和成员名称,例如 [ClassInitialize]、TestContext.WriteLine 和 Assert.IsExactInstanceOfType<T>;“已转换特性”或“已迁移输出”这类笼统说法不能证明行为一致性。

最终回复保持简洁并聚焦结果:

  • **已更改:**列出文件名及应用的具体高风险映射。
  • **已验证:**提供最终测试命令,以及已发现/通过/失败/跳过的数量。
  • **已保留:**说明未更改的目标框架和测试平台,以及任何夹具或并行化作用域决策。
  • **待处理:**指出需手动跟进的事项,或说明没有。

完成标准

  • 已确认当前的 xUnit 版本和测试平台
  • 已转换 xUnit 包和源代码构造
  • 目标框架和测试平台保持不变
  • 明确说明了 fixture 作用域和实际并行化决策;如果无法观察到并发,则应说明有理据的省略原因
  • 构建成功
  • 测试发现结果和结果计数与基线一致
  • 对于不受支持的自定义扩展点,应明确指出,而不是进行近似处理

后续步骤

如果用户还希望使用 MTP,请单独运行 migrate-vstest-to-mtp。只有在确认行为一致后,才使用 writing-mstest-tests 来润色转换后的 MSTest 代码。

评论

支持 Markdown