ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

xunit.v3 迁移实战指南:将 .NET 测试项目从 xUnit.net v2 平滑升级到 xUnit.net v3

2026/9/18 4:37:46 拓冰建站 浏览量
xunit.v3 迁移实战指南:将 .NET 测试项目从 xUnit.net v2 平滑升级到 xUnit.net v3 xunit.v3 迁移实战指南将 .NET 测试项目从 xUnit.net v2 平滑升级到 xUnit.net v3【免费下载链接】skillsRepository for skills to assist AI coding agents with .NET and C#项目地址: https://gitcode.com/GitHub_Trending/skills17/skillsxUnit.net v3 是 xUnit.net 测试框架的下一代版本重构了包结构、运行器与扩展点从xunitv2 包直接升级到xunit.v3会遭遇编译错误、破坏性 API 变更与运行器配置问题。本文基于 dotnet-skills 仓库中的migrate-xunit-to-xunit-v3技能文档SKILL.md给出从 v2 到 v3 的完整迁移工作流包引用映射、OutputTypeExe、VSTest/MTP 运行器保留、CPM 中心包管理适配以及async void、类型化属性、自定义 Fact/Theory/BeforeAfterTest 属性、SkippableFact、Combinatorial/StaFact 配套包等破坏性变更的系统化处理。读完本文你将能独立完成一次可构建、可发现、可运行的 xUnit.net v3 迁移并理解每一步背后的设计动机与验证标准。迁移目标与适用边界迁移的最终形态xUnit.net v3 迁移的完成标准非常明确解决方案中所有测试项目引用xunit.v3.*包族、项目能够干净编译、且全部测试以与迁移前相同的结果通过。注意这里特别强调相同的结果——迁移不应改变测试语义也不应悄悄吞掉或新增跳过。何时使用本技能将测试项目从xunitv2系列包升级到xunit.v3更新 xunit 包引用到 v3 后需要解决随之而来的编译错误。何时不要使用本技能框架间迁移在 MSTest、NUnit 与 xUnit.net 之间切换属于完全不同的工作应使用仓库中对应的migrate-*-to-mstest系列技能如 migrate-nunit-to-mstest而不是本技能仅运行器迁移从 VSTest 迁移到 Microsoft.Testing.PlatformMTP应使用 migrate-vstest-to-mtp其中也包含 xUnit v3 的 MTP 过滤器语法说明项目已经引用xunit.v3迁移已完成直接报告无需操作即可。输入输入是否必需说明测试项目或解决方案否在当前工作目录中发现.csproj、.sln、.slnx、中心属性文件与源码只有未发现任何目标或目标有歧义时才询问工作区与完成契约技能激活不等于完成。对于迁移/修复/更新类请求必须在同一任务中检查暂存文件、编辑文件并运行测试。要点如下技能基础目录只包含指引应搜索当前工作目录并按返回的路径打开文件若某个工具拒绝刚搜索到的路径改用其他可用的读取器/编辑器重试而不是断定文件缺失不要在工作区发现能定位路径的情况下让用户提供路径一次性盘点项目/中心包文件与所有受影响源码——仅做包级迁移而在源码中遗留 v2-only API 是不完整的结束时报告检测到的源码版本与运行器、精确的包兼容集、变更的文件、发现/通过/失败/跳过的测试计数以及平台相关结果。没有测试发现的构建不算成功包的可用性是经验事实记录配置的源查询或解析出的包图以及一次成功的 restore。最终结果必须说明所选精确版本是如何被证明可用的而不是只报一个版本号让评审者去猜。迁移前的决策矩阵开始编辑前先运行一次预检根据检测到的状态决定行动检测到的状态必需动作项目引用xunit.v3具备所需的可执行文件/运行器配置无残留 v2 包或 v2-only API 模式现有测试命令通过停止迁移已完成。不更新版本、不创建 props 文件、不修改源码报告已核验的无操作结果。若仍有必要的 v3 适配只做那一处修复而不是把包引用本身当作完成xUnit v2 使用YTest.MTP.XUnit2保留 MTP移除该 shim设置UseMicrosoftTestingPlatformRunnertrue不要添加xunit.runner.visualstudio也不要设置IsTestingPlatformApplicationfalsexUnit v2 未使用 MTP shim保留 VSTest保留/更新xunit.runner.visualstudio并设置IsTestingPlatformApplicationfalse自定义类型派生自BeforeAfterTestAttribute保留该继承关系及其行为。为两个 override 添加IXunitTest参数并传给base.Before/base.After不要用直接实现接口的方式替换子类基于 Type 的 collection/orderer 属性指向自定义类型同时迁移属性语法与被引用类型的 v3 契约。collection factory 必须实现 xUnit v3 的IXunitTestCollectionFactory行为只让属性编译通过而留下空工厂不算完整迁移存在配套包将xunit.v3、Xunit.Combinatorial、Xunit.StaFact 作为一组兼容集从配置源解析。若最新的 xunit.v3 主版本在源上没有兼容的稳定配套包选择最新的兼容 xunit.v3 主版本并说明固定原因。验证测试发现而不只是编译OutputTypeExe使net*-windows项目在非 Windows 主机上失败在需要跨平台构建时添加EnableWindowsTargetingtrue后重跑。不要把这种迁移引发的失败当作既有问题忽略版本必须从配置的包源解析不要凭产品名里的v3猜测版本也不要去更新无关包。只修改包含适用规则所要求的包、属性或源码构造的文件。Central Package Management 项目的读回检查编辑完 CPM 项目后同时读回Directory.Packages.props与项目文件确认PackageVersion拥有版本号重命名后的PackageReference无版本号OutputTypeExe生效。仓库的 CPM 迁移夹具正是这一场景的实证migrate-xunit-v2-packages-managed-via-central-package-manage 下的Directory.Packages.props以ManagePackageVersionsCentrallytrue管理xunit2.9.3 与xunit.runner.visualstudio2.8.2对应的求值测试见 eval.yaml 的对应场景会校验PackageVersion条目被更新为xunit.v3、csproj 中的PackageReference被重命名且去版本化并最终以dotnet test -p:TreatWarningsAsErrorstrue是否通过来裁决。Step 1识别 xUnit.net 项目并验证兼容性搜索引用 xUnit.net v2 包的测试项目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 风格格式。仓库夹具 detect-incompatible-target-framework-and-stop-migration 中的LegacyTests.csproj目标是net462——低于 v3 要求的最低 .NET Framework 4.7.2。对应求值场景eval.yaml要求识别出 net462 低于最低要求、停止迁移、不把包引用更新为xunit.v3并建议先升级目标框架。Step 2更新包引用按以下映射更新所有PackageReference/PackageVersion项v2 包名v3 处理方式xunit→xunit.v3xunit.abstractions彻底移除xunit.assert→xunit.v3.assertxunit.core→xunit.v3.corexunit.extensibility.core与xunit.extensibility.execution→xunit.v3.extensibility.core同一项目中同时引用两个时合并为单一条目因为 v3 中两个包已合并随后查询配置的包源固定实际存在的最新稳定版本。xunit.runner.visualstudio只在 VSTest 项目中更新MTP 项目不要添加它。仓库夹具 migrate-basic-xunit-net-v2-project-to-v3 给出了典型 v2 项目形态TestProject.csproj引用xunit2.9.3 与xunit.runner.visualstudio2.8.2同时搭配Microsoft.NET.Test.Sdk18.3.0。而 consolidate-xunit-extensibility-packages-and-remove-xunit-ab 夹具则覆盖了同时引用xunit.extensibility.core、xunit.extensibility.execution与xunit.abstractions的合并场景其求值断言xunit.v3.extensibility出现、using Xunit.Abstractions;消失。Step 3设置OutputType为Exe在每个测试项目测试库项目除外的项目文件中设置OutputTypePropertyGroup OutputTypeExe/OutputType /PropertyGroupxUnit.net v3 的测试程序集是自承载的可执行文件这一步不可或缺。根据解决方案结构可能有集中放置的位置若所有测试项目共享或可以共享一个公共Directory.Build.props把该属性加在那里。注意OutputType不应添加到Directory.Build.targets若所有测试项目共享命名模式如*.Tests.csproj可在Directory.Build.props中添加仅作用于这些项目的条件属性组例如OutputType Condition$(MSBuildProjectName.EndsWith(.Tests))Exe/OutputType按需调整条件以精确命中测试项目否则在每个测试项目文件中单独添加该属性。Step 4配置测试平台保留 v2 时期使用的同一测试平台是迁移的基本原则xUnit.net v2 除使用YTest.MTP.XUnit2的项目外一律使用 VSTest。情况 A项目曾引用YTest.MTP.XUnit2MTP 场景完全移除对YTest.MTP.XUnit2的引用在已有的共享Directory.Build.props无共享 props 文件时放在测试项目中设置UseMicrosoftTestingPlatformRunnertrue/UseMicrosoftTestingPlatformRunner不要添加xunit.runner.visualstudio——它是 VSTest 运行器会削弱平台保留。仓库夹具 migrate-project-with-ytest-mtp-xunit2-to-xunit-net-v3-preser 完整呈现了这一场景v2 项目同时引用xunit2.9.3 与YTest.MTP.XUnit21.0.0共享Directory.Build.props目前只有IsPackablefalse。其求值用dotnet msbuild TestProject.csproj -getProperty:UseMicrosoftTestingPlatformRunner验证属性最终为true并断言TestProject.csproj中不出现xunit.runner.visualstudio、输出中不出现IsTestingPlatformApplicationfalse。情况 B项目未引用YTest.MTP.XUnit2常见情况VSTest 场景在已有的共享Directory.Build.props无共享 props 文件时直接放在测试项目中设置IsTestingPlatformApplicationfalse/IsTestingPlatformApplication不要仅为单个项目创建仓库级 props 文件——这会把项目留在 VSTest 上。Step 5移除Xunit.Abstractionsusing在 C# 文件中查找using Xunit.Abstractions;指令并彻底删除。v3 中xunit.abstractions包被移除其 API 已并入核心保留该 using 将导致编译失败。Step 6处理async void破坏性变更按需xUnit.net v3不再支持async void测试方法此类代码将无法编译。搜索所有以async void声明的测试方法可通过[Fact]、[Theory]或其他测试属性识别改为async Task。夹具 convert-async-void-test-methods-to-async-task 的AsyncTests.cs中[Fact] GetUser_ReturnsExpectedName与[Theory] ProcessItem_Completes均声明为async void需要改为async Task。其求值要求file-not-contains async void、output-matches async Task。在最终结果中必须说明源码变更的原因xUnit.net v3 拒绝async void测试因此每个受影响方法现在返回Task而不是只报告机械替换同时说明精确包版本是如何从配置源解析的。Step 7处理属性的类型化破坏性变更按需xUnit.net v3 中部分属性从两个字符串完全限定类型名 程序集名改为接受System.Type。受影响属性CollectionBehaviorAttributeTestCaseOrdererAttributeTestCollectionOrdererAttributeTestFrameworkAttribute例如[assembly: CollectionBehavior(MyNamespace.MyCollectionFactory, MyAssembly)]必须转换为[assembly: CollectionBehavior(typeof(MyNamespace.MyCollectionFactory))]。夹具 convert-string-based-attribute-constructors-to-typeof-syntax 的OrderedTests.cs使用了[TestCaseOrderer(MyApp.Tests.AlphabeticalOrderer, TestProject)]需要转换为typeof()语法对应求值还要求CollectionBehavior不再以字符串构造并最终通过dotnet test -p:TreatWarningsAsErrorstrue。若类型化属性指向自定义类型如 collection factory还需同步迁移被引用类型的 v3 契约——例如实现 xUnit v3 的IXunitTestCollectionFactory行为仅让属性编译通过而留下空工厂不算完整迁移。Step 8自定义 Fact/Theory 属性的源码信息按需识别所有继承自FactAttribute或TheoryAttribute的自定义属性。v3 要求这些属性提供源码信息。例如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) { } }夹具 update-custom-factattribute-to-include-source-information-pa 的CustomAttributes.cs中RetryFactAttribute继承FactAttribute与ConditionalTheoryAttribute继承TheoryAttribute都只有无参构造需要按上述模式补充[CallerFilePath]与[CallerLineNumber]参数并转发给base()。报告完成前必须读回每个受影响的FactAttribute/TheoryAttribute派生构造器逐个命名类型确认 caller-info 参数与对应的base(sourceFilePath, sourceLineNumber)转发都已存在。仅测试运行通过不能证明源码信息已被正确传播。Step 9继承BeforeAfterTestAttribute的签名更新按需识别所有继承自BeforeAfterTestAttribute的自定义属性。v3 改变了方法签名之前Before/After的 override 长这样public override void Before(MethodInfo methodUnderTest) { // 自定义逻辑 base.Before(methodUnderTest); // 自定义逻辑 } public override void After(MethodInfo methodUnderTest) { // 自定义逻辑 base.After(methodUnderTest); // 自定义逻辑 }必须改为public override void Before(MethodInfo methodUnderTest, IXunitTest test) { // 自定义逻辑 base.Before(methodUnderTest, test); // 自定义逻辑 } public override void After(MethodInfo methodUnderTest, IXunitTest test) { // 自定义逻辑 base.After(methodUnderTest, test); }保持BeforeAfterTestAttribute基类、保留 override 修饰符、保留现有 base 调用及其相对于自定义逻辑的顺序。直接实现IBeforeAfterTestAttribute接口虽然能编译但这不是机械的 v2→v3 迁移且可能丢弃基类行为。夹具 update-beforeaftertestattribute-overrides-with-ixunittest-pa 的DatabaseSetupAttribute.cs正是 v2 形态Before(MethodInfo)/After(MethodInfo)。其求值断言文件保留: BeforeAfterTestAttribute、出现IXunitTest、且base.Before(与base.After(均存在。报告完成前应读出属性文件并引用实际的Before(MethodInfo, IXunitTest)与After(MethodInfo, IXunitTest)签名明确确认base.Before与base.After收到同一个IXunitTest参数——仅笼统声称已更新 override是证据不足的。Step 10处理新的 xUnit 分析器警告按需xunit.v3 引入了新的分析器警告最典型的是xUnit1051对接受CancellationToken的方法使用TestContext.Current.CancellationToken。若项目中出现此类警告应一并处理。Step 11迁移Xunit.SkippableFact按需若项目引用了Xunit.SkippableFact包彻底移除该包引用然后消除来自该包的 API 用法将SkippableFact属性改为常规Fact将SkippableTheory属性改为常规Theory将Skip.If调用改为Assert.SkipWhen将Skip.IfNot调用改为Assert.SkipUnless。夹具 migrate-xunit-skippablefact-to-xunit-net-v3-built-in-skip-ap 的ConditionalTests.cs展示了典型用法[SkippableFact]方法内Skip.IfNot(OperatingSystem.IsWindows())[SkippableTheory]方法内Skip.If(...)。其求值要求输出匹配Assert.SkipWhen|Assert.SkipUnless、不匹配SkippableFact...Version并且不创建新的Directory.Build.props用test ! -e Directory.Build.props验证。当夹具允许时验证两条分支默认条件应报告预期的跳过原因启用条件应执行并通过——仅靠 grep 加一次普通通过运行无法证明运行时跳过语义被保留。此转换限定在既有项目/中心包文件与包含这些 API 的源文件中不要仅为完成配套包迁移而新建Directory.Build.props任何必需的运行器属性在无共享 props 文件时都应放进既有测试项目。Step 12更新配套包按需从配置源查询相互兼容的集合而不是独立解析每个包Xunit.Combinatorial1.x 应升级到 2.x 或更高Xunit.StaFact1.x 应升级到与所选xunit.v3主版本兼容的版本线不要依据产品名或主版本号相同来推断配套包兼容性。应使用包依赖约束与配置源上实际可用的版本并通过测试发现证明所选集合有效切换到可执行输出后从 Linux/macOS 构建net*-windows项目时若需要跨目标平台设置EnableWindowsTargetingtrue运行测试将预期的平台跳过如 Linux 上的 STA 测试与失败区分开来确认。夹具 update-xunit-combinatorial-and-xunit-stafact-companion-packa 对应此场景其求值要求Xunit.Combinatorial从 1.x 升级到兼容的 2.x、Xunit.StaFact升级到配置源上兼容所选 xunit.v3 主版本的稳定版本并同步迁移核心xunit包、设置OutputTypeExe。Step 13构建并验证构建解决方案并修复所有剩余编译错误然后运行dotnet test确认所有测试以与迁移前相同的结果通过。仓库的求值体系eval.yaml对几乎每个场景都以dotnet test -p:TreatWarningsAsErrorstrue且退出码为 0 作为最终裁决条件——这从侧面印证了无测试发现的构建不算成功的完成契约迁移的终点不是编译通过而是测试被真正发现、执行并通过。从源码结构看技能与求值的对应关系本技能隶属于dotnet-test-migration插件plugin.json其配套求值目录 tests/dotnet-test-migration/migrate-xunit-to-xunit-v3 为文档中的每一步提供了可执行的验证夹具基础迁移、CPM 迁移、YTest.MTP shim 迁移覆盖 Step 2~5 的包与运行器配置convert-async-void-test-methods-to-async-task覆盖 Step 6convert-string-based-attribute-constructors-to-typeof-syntax覆盖 Step 7update-custom-factattribute-to-include-source-information-pa覆盖 Step 8update-beforeaftertestattribute-overrides-with-ixunittest-pa覆盖 Step 9migrate-xunit-skippablefact-to-xunit-net-v3-built-in-skip-ap覆盖 Step 11update-xunit-combinatorial-and-xunit-stafact-companion-packa覆盖 Step 12detect-incompatible-target-framework-and-stop-migration验证 Step 1 的硬性停止条件recognize-project-already-on-xunit-net-v3-no-migration-neede验证决策矩阵中的已迁移即停止分支——其求值通过 diff 基线文件确认迁移过程未做任何多余修改。可见本技能的 13 步工作流、决策矩阵与完成契约均可在 tests/dotnet-test-migration/migrate-xunit-to-xunit-v3 中找到一一对应的可运行验证这为读者复现与自测迁移结果提供了现成的参考基线。小结xUnit.net v3 迁移本质上是一套先识别、再分层处理的确定性流程先验证目标框架与包形态Step 1再做包映射与OutputTypeExeStep 2~3随后按原运行器配置保留 VSTest 或 MTPStep 4最后按代码模式逐项处理async void、类型化属性、自定义属性、分析器警告与配套包Step 5~12。每完成一步都以读回文件 运行测试发现而非仅编译通过作为验证标准最终交付一个包兼容集可溯源、测试结果与迁移前一致的解决方案。【免费下载链接】skillsRepository for skills to assist AI coding agents with .NET and C#项目地址: https://gitcode.com/GitHub_Trending/skills17/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考