关于 migrate-xunit-to-xunit-v3
用于将 .NET 测试项目从 xUnit.net v2 迁移到 v3 的技能,面向维护现有 xUnit 测试套件的开发者。它涵盖包与运行器配置调整,以及受影响源码的迁移和测试验证。
主要功能
- 01迁移 xUnit.net 包引用
- 02保留原有测试平台
- 03检查并修复 v3 API 变更
- 04执行测试并核实结果
适用场景
- 01升级测试项目至 xUnit.net v3
- 02修复切换 v3 后的编译错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill migrate-xunit-to-xunit-v3安装后在 Claude Code 中输入/migrate-xunit-to-xunit-v3确认已加载。
详细介绍
xunit.v3 迁移
将 .NET 测试项目从 xUnit.net v2 迁移到 xUnit.net v3。最终结果应是:所有测试项目都引用 xunit.v3.* 包,能够顺利编译,并且所有测试通过且结果与迁移前一致。
适用场景
- 将测试项目从
xunit(v2)包升级到xunit.v3 - 更新 xunit 包引用到 v3 后,解决编译错误
不适用场景
- 在不同测试框架之间迁移(例如从 MSTest 或 NUnit 迁移到 xUnit.net)——这是完全不同的工作
- 从 VSTest 迁移到 Microsoft.Testing.Platform——请使用
migrate-vstest-to-mtp - 项目已经引用
xunit.v3——迁移已完成
输入
| 输入 | 必需 | 描述 |
|---|---|---|
| 测试项目或解决方案 | 否 | 在当前工作目录中查找 .csproj、.sln、.slnx、central props 和源代码;仅在未找到任何内容或目标不明确时询问 |
工作区与完成要求
- 激活技能不代表任务已完成。对于迁移/修复/更新请求,应检查暂存文件、编辑这些文件,并在同一任务中运行测试。
- 技能基础目录仅包含指导内容。搜索当前工作目录,并按返回的路径原样打开文件。
- 如果工具拒绝已搜索到的路径,应先判断拒绝原因。只有在明确确认是读取器/路径规范化限制,且访问已获授权时,才允许通过其他获准的读取器/编辑器重试。
- 遇到权限、策略或内容排除导致的拒绝时,应停止访问该路径并报告阻碍。切勿通过其他工具、shell 命令、别名或 Agent 绕过拒绝,也不得推断或重建受限内容。
- 如果拒绝原因不明确,应将该路径视为不可用,而非不存在。仅继续进行其他独立获准的工作,并明确说明迁移未完成。
- 只要仍可通过工作区发现文件,就不要要求用户提供路径。
- 一次性盘点项目/中央包文件以及所有受影响的源代码。如果仍存在仅适用于 v2 的 API,则仅迁移包并不完整。
- 结束时说明检测到的源版本和运行器、精确的包兼容组合、已更改文件、已发现/通过/失败/跳过的数量,以及任何特定于平台的结果。未发现测试的构建不算成功。
- 包是否有效需以实际验证为准:记录已配置的源查询或已解析的包依赖关系图,并确认还原成功。在最终结果中说明如何验证所选精确版本确实可用;不要只给出版本号并让评审者或用户自行判断其是否存在。
工作流程
提交策略: 除非用户提出要求,否则不要创建提交。确保项目配置和源代码编辑在差异中逻辑上可分,但要完成并验证整个迁移请求。
优先级: 每次迁移都必须执行步骤 1–5。步骤 6–12 为条件步骤——仅在与项目代码模式相关时应用。跳过不适用的步骤。
会改变结果的决策
编辑前运行此预检:
| 检测到的状态 | 必需操作 |
|---|---|
项目引用 xunit.v3,具有必需的可执行文件/运行器配置,不再包含 v2 包或仅适用于 v2 的 API 模式,且现有测试命令通过 | 停止:迁移已完成。不要更新版本、创建 props 文件或修改源代码。报告已验证的无操作结果。如果仍需进行任何必需的 v3 适配,只做该项修复,不要仅凭包引用就视为迁移完成。 |
xUnit v2 使用 YTest.MTP.XUnit2 | 保留 MTP:移除此 shim,设置 UseMicrosoftTestingPlatformRunner=true,不要添加 xunit.runner.visualstudio 或 IsTestingPlatformApplication=false。 |
| xUnit v2 未使用 MTP shim | 保留 VSTest:保留/更新 xunit.runner.visualstudio,并设置 IsTestingPlatformApplication=false。 |
自定义类型派生自 BeforeAfterTestAttribute | 保留此继承关系及其行为。为两个重写方法都添加 IXunitTest 参数,并将其传递给 base.Before/base.After;不要将子类替换为直接实现接口。 |
| 基于类型的集合/排序器属性指向自定义类型 | 迁移属性语法和所引用类型的 v3 契约。对于集合工厂,实现 xUnit v3 所要求的 IXunitTestCollectionFactory 行为;仅编译该属性但保留空工厂,不算完成迁移。 |
| 存在配套包 | 从已配置的源中,将 xunit.v3、Xunit.Combinatorial 和 Xunit.StaFact 解析为兼容的一组版本。如果这些源中最新的 xunit.v3 主版本没有兼容的稳定版配套包,则选择最新的兼容 xunit.v3 主版本,并说明固定版本的原因。验证测试发现,而不仅仅是编译。 |
OutputType=Exe 导致 net*-windows 项目在非 Windows 主机上失败 | 如果计划进行跨平台构建,则添加 EnableWindowsTargeting=true,然后重新运行。不要将此迁移引起的失败视为原有问题而忽略。 |
从已配置的包源解析包版本。不要根据产品名称中的“v3”猜测版本,也不要更新无关的包。只修改包含适用规则所要求的包、属性或源代码结构的文件。
编辑中央包管理项目后,重新读取 Directory.Packages.props 和项目文件。确认版本由 PackageVersion 管理,重命名后的 PackageReference 不包含版本,并且 OutputType=Exe 已生效。
步骤 1:识别 xUnit.net 项目并验证兼容性
搜索引用 xUnit.net v2 包的测试项目:
xunitxunit.abstractionsxunit.assertxunit.corexunit.extensibility.corexunit.extensibility.executionxunit.runner.visualstudio
务必检查项目文件、MSBuild props 和 targets 文件(例如 Directory.Build.props、Directory.Build.targets 和 Directory.Packages.props)中的包引用。
验证目标框架兼容性:xUnit.net v3 要求 .NET 8+ 或 .NET Framework 4.7.2+。对于测试库项目,也支持 .NET Standard 2.0。如果任何测试项目的目标框架不兼容,请在此处停止,并告知用户先升级目标框架。同时验证项目使用的是 SDK 样式格式。
步骤 2:更新包引用
-
根据以下映射,将
PackageReference或PackageVersion项更新为新的包名称:xunit→xunit.v3xunit.abstractions→ 完全移除xunit.assert→xunit.v3.assertxunit.core→xunit.v3.corexunit.extensibility.core和xunit.extensibility.execution→xunit.v3.extensibility.core(如果项目同时引用这两个包,请合并为单个条目,因为这两个包已合并)
-
查询已配置的包源,并固定为实际存在的最新稳定版本。仅对 VSTest 项目更新
xunit.runner.visualstudio;不要将其添加到 MTP 项目。
步骤 3:将 OutputType 设为 Exe
在每个测试项目(测试库项目除外)中,于项目文件内将 OutputType 设为 Exe:
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
根据当前解决方案的情况,可以在集中位置添加此设置。例如:
- 如果所有测试项目都共用(或可以共用)一个
Directory.Build.props,请在其中添加<OutputType>Exe</OutputType>属性。请注意,不应将OutputType添加到Directory.Build.targets。 - 如果所有测试项目都遵循相同的名称模式(例如
*.Tests.csproj),请在Directory.Build.props中添加一个条件属性组,使其仅应用于这些项目,例如<OutputType Condition="$(MSBuildProjectName.EndsWith('.Tests'))">Exe</OutputType>。请根据需要调整条件,确保只针对测试项目。 - 否则,请分别将
<OutputType>Exe</OutputType>属性添加到每个测试项目文件中。
步骤 4:配置测试平台
保留 xUnit.net v2 所使用的测试平台。xUnit.net v2 始终使用 VSTest,除非项目使用了 YTest.MTP.XUnit2。
- 如果项目引用了
YTest.MTP.XUnit2:- 完全移除对
YTest.MTP.XUnit2的引用。 - 在现有的共享
Directory.Build.props中,或在没有共享 props 文件时于测试项目中,设置<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>。 - 不要添加
xunit.runner.visualstudio;它是 VSTest 运行器,会削弱对原有平台的保留。
- 完全移除对
- 如果项目未引用
YTest.MTP.XUnit2(常见情况):- 在现有的共享
Directory.Build.props中,或在没有共享 props 文件时直接在测试项目中,设置<IsTestingPlatformApplication>false</IsTestingPlatformApplication>。不要仅为一个项目创建仓库级 props 文件。这样可以让项目继续使用 VSTest。
- 在现有的共享
步骤 5:移除 Xunit.Abstractions using 指令
查找 C# 文件中的所有 using Xunit.Abstractions; 指令,并将其完全移除。
步骤 6:处理 async void 破坏性变更(如适用)
在 xUnit.net v3 中,不再支持 async void 测试方法,并且这类方法会导致编译失败。搜索所有声明为 async void 的测试方法,并将其改为 async Task。可通过 [Fact]、[Theory] 或其他测试特性识别测试方法。
在最终结果中,说明源代码更改的原因:xUnit.net v3 不接受
async void 测试,因此每个受影响的方法现在都返回 Task。不要只报告机械性的替换。还要说明如何从已配置的源解析出确切的包版本。
步骤 7:处理特性的破坏性变更(如适用)
在 xUnit.net v3 中,某些特性已更新为接受 System.Type,而不是两个字符串(完全限定类型名称和程序集名称)。这些特性包括:
CollectionBehaviorAttributeTestCaseOrdererAttributeTestCollectionOrdererAttributeTestFrameworkAttribute
例如,[assembly: CollectionBehavior("MyNamespace.MyCollectionFactory", "MyAssembly")] 必须改为 [assembly: CollectionBehavior(typeof(MyNamespace.MyCollectionFactory))]。
步骤 8:继承自 FactAttribute 或 TheoryAttribute(如适用)
确认是否有任何自定义特性继承自 FactAttribute 或 TheoryAttribute。这些自定义用户定义特性现在必须提供源信息。例如,如果特性原本如下所示:
internal sealed class MyFactAttribute : FactAttribute
{
public MyFactAttribute()
{
}
}
则必须将其改为:
internal sealed class MyFactAttribute : FactAttribute
{
public MyFactAttribute(
[CallerFilePath] string? sourceFilePath = null,
[CallerLineNumber] int sourceLineNumber = -1
) : base(sourceFilePath, sourceLineNumber)
{
}
}
报告完成前,请逐一复核所有受影响的 FactAttribute 和 TheoryAttribute 派生构造函数。列出每种类型,并确认调用方信息参数和对应的 base(sourceFilePath, sourceLineNumber) 转发都已存在。测试通过本身并不能证明源信息已正确传递。
步骤 9:继承自 BeforeAfterTestAttribute(如适用)
确认是否有任何自定义特性继承自 BeforeAfterTestAttribute。这些自定义用户定义特性必须更新其方法签名。此前,它们的 Before/After 重写方法可能如下所示:
public override void Before(MethodInfo methodUnderTest)
{
// Possibly some custom logic here
base.Before(methodUnderTest);
// Possibly some custom logic here
}
public override void After(MethodInfo methodUnderTest)
{
// Possibly some custom logic here
base.After(methodUnderTest);
// Possibly some custom logic here
}
则必须将其改为:
public override void Before(MethodInfo methodUnderTest, IXunitTest test)
{
// Possibly some custom logic here
base.Before(methodUnderTest, test);
// Possibly some custom logic here
}
public override void After(MethodInfo methodUnderTest, IXunitTest test)
{
// Possibly some custom logic here
base.After(methodUnderTest, test);
// Possibly some custom logic here
}
保留 BeforeAfterTestAttribute 基类和重写修饰符,并保留现有的基类调用及其相对于自定义逻辑的顺序。直接实现 IBeforeAfterTestAttribute 可能可以编译,但这并非从 v2 到 v3 的机械式迁移,而且可能丢弃基类行为。
报告完成前,请阅读最终的特性文件,并引用实际的 Before(MethodInfo, IXunitTest) 和
After(MethodInfo, IXunitTest) 签名。明确确认 base.Before 和 base.After 接收的是同一个 IXunitTest 参数;仅笼统声称已更新重写方法不足以作为证据。
步骤 10:处理新的 xUnit 分析器警告(如适用)
xunit.v3 引入了新的分析器警告。最值得注意的是 xUnit1051(对于接受 CancellationToken 的方法,使用 TestContext.Current.CancellationToken)。如果存在这类警告,请予以处理。
步骤 11:迁移 Xunit.SkippableFact(如适用)
如果存在对 Xunit.SkippableFact 的包引用,请将这些包引用全部移除。
然后,按照以下步骤消除对已移除包所提供 API 的使用:
- 将所有
SkippableFact特性替换为常规的Fact特性。 - 将所有
SkippableTheory特性替换为常规的Theory特性。 - 将
Skip.If方法调用改为Assert.SkipWhen。 - 将
Skip.IfNot方法调用改为Assert.SkipUnless。
如果测试夹具允许,请验证两个分支:默认条件应报告预期的跳过原因,启用条件则应执行并通过。仅进行 grep 检查和笼统地运行测试并通过,无法证明运行时跳过语义得到了保留。
此转换仅限于现有项目文件/集中式包文件,以及包含这些 API 的源文件。不要仅为迁移此配套包而新建 Directory.Build.props;如果不存在共享 props 文件,任何必需的运行器属性都应放在现有测试项目中。
步骤 12:更新配套包(如适用)
- 查询已配置的软件源,获取一组相互兼容的版本,而不是分别解析每个包。
Xunit.Combinatorial1.x 应升级到 2.x 或更高版本,而Xunit.StaFact1.x 应升级到与所选xunit.v3主版本兼容的版本系列。 - 不要根据产品名称或匹配的主版本号推断配套包是否兼容。请使用包依赖约束和已配置软件源中可用的版本,并通过测试发现来证明所选版本组合可行。
- 在 Linux/macOS 上构建
net*-windows项目并切换到可执行输出时,如果有意进行跨目标构建,请设置EnableWindowsTargeting=true。 - 运行测试,并将预期的平台跳过(例如 Linux 上的 STA 测试)与失败分别确认。
步骤 13:构建并验证
构建解决方案并修复所有剩余的编译错误。运行 dotnet test,确认所有测试均通过,且结果与迁移前相同。