大模型应用
将 .NET 测试项目从 VSTest 迁移到…
当 .NET 测试或 CI 需要从 VSTest 切换到 Microsoft.Testing.Platform(MTP),或 MTP 迁移后行为异常(如退出码 8、零测试)时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
将 .NET 测试项目从 VSTest 通用
解决思路
- 先检查项目、Directory.Build.props、global.json 和 CI 文件,再决定方案;先确定框架和 SDK 模式:.NET 9 及更早使用兼容属性加 `--` 分隔符,.NET 10 原生 MTP 使用 global.json、移除该属性并直接传 MTP 参数。
- 绝不在同一解决方案或运行配置中混用基于 VSTest 和基于 MTP 的 .NET 测试项目(不受支持)。
- 识别每个测试项目的框架:MSTest 引用 `MSTest`/`MSTest.TestAdapter` 或使用 `MSTest.Sdk`(`<IsTestApplication>` 未设为 false);NUnit 引用 `NUnit3TestAdapter`;xUnit.net 引用 `xunit` 和 `xunit.runner.visualstudio`。注意仅 `MSTest.TestFramework` 是库依赖,不是测试项目。
- 从 `global.json` 或用户明确上下文确定仓库/CI 使用的 SDK;仅当两者都不存在时才回退到 `dotnet --version`,不要让宿主 SDK 覆盖已声明的 .NET 8/9 CI 目标。
- 检查解决方案或仓库根目录是否存在 `Directory.Build.props`,所有 MTP 属性应集中放在那里以保持一致。
- 检查 CI 脚本或流水线定义中是否使用 `vstest.console.exe`。
- 检查 CI 脚本中 VSTest 专用的 `dotnet test` 参数:`--filter`、`--logger`、`--collect`、`--settings`、`--blame*`。
- 运行 `dotnet test` 建立迁移前测试通过/失败数量的基线。
- 在解决方案或仓库根目录的 `Directory.Build.props` 中集中设置 MTP runner 属性,避免部分项目用 VSTest、部分用 MTP 的不受支持场景。
- MTP 要求测试项目设置 `<OutputType>Exe</OutputType>`;只有 `MSTest.Sdk` 会自动设置。其他情况(MSTest NuGet 包加 `EnableMSTestRunner`、NUnit 加 `EnableNUnitRunner`、xUnit.net 加 `YTest.MTP.XUnit2`)优先在 `Directory.Build.props` 中用只针对测试项目的条件集中设置 `<OutputType>Exe</OutputType>`;若无法可靠只针对测试项目,则按项目设置作为可接受的例外。
- 在 `Directory.Build.props` 中不要使用 `Condition="'$(IsTestProject)' == 'true'"`,因为 `IsTestProject` 在测试 SDK targets 中更晚才设置;改用早期可用的属性如 `MSBuildProjectName`,例如 `<PropertyGroup Condition="$(MSBuildProjectName.EndsWith('.Tests'))"><OutputType>Exe</OutputType></PropertyGroup>`,并按仓库命名约定调整条件(如 `.EndsWith('Tests')`、`.Contains('.Test')`)。
- 在同一条件中放入所有适用的集中属性:`OutputType`、框架 runner 选择属性,以及 .NET 9 及更早的 `TestingPlatformDotnetTestSupport`;不要留下仍影响生产项目的无条件 runner 属性。
- MSTest 使用 NuGet 包(3.2.0+)时设置 `<EnableMSTestRunner>true</EnableMSTestRunner>` 和 `<OutputType>Exe</OutputType>`;确保引用 MSTest 3.2.0 或更高版本,若已是 3.2.0+ 则无需为 MTP 迁移升级 MSTest 版本。
- MSTest 使用 `MSTest.Sdk` 时 MTP 默认启用,无需 `EnableMSTestRunner` 或 `OutputType Exe`;唯一操作是若项目有 `<UseVSTest>true</UseVSTest>` 则移除它。
- NUnit 需要 `NUnit3TestAdapter` 5.0.0 或更高版本:更新 `NUnit3TestAdapter` 到 5.0.0+,并设置 `<EnableNUnitRunner>true</EnableNUnitRunner>` 和 `<OutputType>Exe</OutputType>`。
- xUnit.net v2 添加 `YTest.MTP.XUnit2` 包引用(如 0.4.0)并设置 `<OutputType>Exe</OutputType>`;该包保留 VSTest `--filter` 语法,无需迁移过滤器,还支持 `--settings`(仅 xunit 特定配置)、`xunit.runner.json`、`--report-trx` 和 `--treenode-filter`。
- xUnit.net v3(`xunit.v3` 包)内置 MTP 支持,设置 `<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>`;注意 xUnit.net v3 在 MTP 上不支持 VSTest `--filter` 语法,必须翻译过滤器。
- 配置 `dotnet test` 集成:.NET 10 SDK 及以后推荐在 `global.json` 添加 `"test": { "runner": "Microsoft.Testing.Platform" }` 使用原生 MTP 模式,参数直接传递(如 `dotnet test --report-trx`);注意 `global.json` 不支持尾随逗号。
- 配置 `dotnet test` 集成:.NET 9 SDK 及更早,在 `Directory.Build.props` 添加 `<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>`,并使用 `--` 分隔构建参数和 MTP 参数,如 `dotnet test --no-build -- --list-tests`。
- 翻译 VSTest 专用参数为 MTP 等价物(构建相关参数如 `-c`、`-f`、`--no-build`、`--nologo`、`-v` 不变):`--test-adapter-path` 不适用;`--blame` 不适用;`--blame-crash` → `--crashdump`(需 `Microsoft.Testing.Extensions.CrashDump`);`--blame-crash-dump-type <TYPE>` → `--crashdump-type <TYPE>`;`--blame-hang` → `--hangdump`(需 `Microsoft.Testing.Extensions.HangDump`);`--blame-hang-dump-type <TYPE>` → `--hangdump-type <TYPE>`;`--blame-hang-timeout <TIMESPAN>` → `--hangdump-timeout <TIMESPAN>`;`--collect "Code Coverage;Format=cobertura"` → `--coverage --coverage-output-format cobertura`;`-d|--diag <LOG_FILE>` → `--diagnostic`;`--filter <EXPRESSION>` → `--filter <EXPRESSION>`(MSTest、NUnit、xUnit.net v2 加 `YTest.MTP.XUnit2` 语法相同;xUnit.net v3 见过滤器迁移);`-l|--logger trx` → `--report-trx`(需 `Microsoft.Testing.Extensions.TrxReport`);`--results-directory <DIR>` 相同;`-s|--settings <FILE>` → `--settings <FILE>`(MSTest 和 NUnit 仍支持 `.runsettings`);`-t|--list-tests` 相同;`-- <RunSettings args>` → `--test-parameter`(仅 MSTest 和 NUnit)。
- 过滤器迁移:MSTest、NUnit 和 xUnit.net v2(加 `YTest.MTP.XUnit2`)的 VSTest `--filter` 语法在 VSTest 和 MTP 上相同,无需更改;xUnit.net v3 原生 MTP 不支持 VSTest `--filter` 语法,必须翻译为原生过滤器选项。
- xUnit.net v3 过滤器标志:`--filter-class "name"` 运行指定类的所有测试(支持 `*` 通配符);`--filter-not-class "name"` 排除指定类;`--filter-method "name"` 运行特定测试方法;`--filter-not-method "name"` 排除特定方法;`--filter-namespace "name"` 运行命名空间内所有测试;`--filter-not-namespace "name"` 排除命名空间;`--filter-trait "name=value"` 运行匹配 trait 的测试;`--filter-not-trait "name=value"` 排除匹配 trait 的测试;单个标志可指定多个值,如 `--filter-class Foo Bar`。
- VSTest 到 xUnit.net v3 过滤器翻译:`FullyQualifiedName~ClassName` → `--filter-class *ClassName*`(子串匹配需通配符);`FullyQualifiedName=Ns.Class.Method` → `--filter-method Ns.Class.Method`(完全限定方法精确匹配);`Name=MethodName` → `--filter-method *MethodName*`(子串匹配需通配符);`Category=Value`(trait)→ `--filter-trait "Category=Value"`;复杂表达式 → `--filter-query "expr"`(使用 xUnit.net 查询过滤器语言)。
- xUnit.net v3 复杂表达式使用 `--filter-query` 路径段语法:`/<assemblyFilter>/<namespaceFilter>/<classFilter>/<methodFilter>[traitName=traitValue]`,每段匹配程序集名、命名空间、类名、方法名,任意段用 `*` 表示匹配全部;文档见 https://xunit.net/docs/query-filter-language。
- 优先使用直接对应的 `--filter-class`/`--filter-method`/`--filter-trait` 标志表达原过滤器;仅在用 `--list-tests` 验证查询后才使用 `--filter-query`,语法接受但选中零测试的查询不算成功翻译。
- 翻译示例:VSTest `dotnet test --filter "FullyQualifiedName~IntegrationTests&Category=Smoke"`;xUnit.net v3 MTP 单独过滤器(AND 行为)`dotnet test -- --filter-class *IntegrationTests* --filter-trait "Category=Smoke"`;查询语言 `dotnet test -- --filter-query "/*/*/*IntegrationTests*/*[Category=Smoke]"`。
- 若 CI 脚本使用 TRX 报告、崩溃转储、挂起转储或覆盖率,添加对应包:`Microsoft.Testing.Extensions.TrxReport`、`Microsoft.Testing.Extensions.CrashDump`、`Microsoft.Testing.Extensions.HangDump`、`Microsoft.Testing.Extensions.CodeCoverage`;在 `Directory.Packages.props` 添加匹配的 `PackageVersion`;无中央包管理时使用从配置源解析的确切版本,保持与所选 MTP 栈兼容,不要复制固定版本。
- 更新 CI/CD 流水线:Azure DevOps 若使用 VSTest 任务(`VSTest@3`),替换为 .NET Core CLI 任务(`DotNetCoreCLI@2`),如 `- task: DotNetCoreCLI@2` 加 `inputs: command: 'test'`、`arguments: '--no-build --configuration Release'`;若已用 `DotNetCoreCLI@2`,按参数翻译更新,.NET 9 及更早记得 `--` 分隔符,如 `arguments: '--no-build -- --report-trx --results-directory $(Agent.TempDirectory)'`。
- GitHub Actions 中用相同的参数翻译更新工作流文件中的 `dotnet test` 调用。
- 若任何脚本直接调用 `vstest.console.exe`,替换为 `dotnet test`;测试项目现在是可执行文件,也可直接运行。
- 处理零测试退出码差异:VSTest 零测试静默成功,MTP 以退出码 8 失败;可选方案:运行时传 `--ignore-exit-code 8`;在 `Directory.Build.props` 添加 `<TestingPlatformCommandLineArguments>$(TestingPlatformCommandLineArguments) --ignore-exit-code 8</TestingPlatformCommandLineArguments>`;或使用环境变量 `TESTINGPLATFORM_EXITCODE_IGNORE=8`。
- 迁移完成并验证后,可选移除仅 VSTest 需要的包:`Microsoft.NET.Test.Sdk`(MTP 不需要,MSTest.Sdk v4 已默认省略)、`xunit.runner.visualstudio`(仅 VSTest 发现 xUnit.net 需要,使用 `YTest.MTP.XUnit2` 时不需要)、`NUnit3TestAdapter` 的 VSTest 专用功能(适配器仍需要但仅用于 MTP runner);若过渡期需保持 VSTest 兼容则保留这些包。
- 验证:运行 `dotnet build` 确认零错误;运行 `dotnet test` 确认所有测试通过;对比迁移前基线的通过/失败数量;直接运行测试可执行文件(如 `./bin/Debug/net8.0/MyTests.exe`)确认可用;验证 CI 流水线产生预期测试结果产物(TRX 文件、代码覆盖率、崩溃转储);测试 Visual Studio(17.14+)或 VS Code 的 Test Explorer 能发现并运行测试。
- 最终结果必须说明框架 runner 选择属性、仓库选择的 SDK 集成模式、确切翻译后的命令、扩展包和验证证据。
- 对于实现请求,编辑文件后验证有效 MSBuild 属性、翻译后的命令、测试数量和请求的产物;对于问题,针对检测到的 SDK 和框架提供一个确切命令/配置,而不是近似等价物的菜单。
- 保留所有构建参数,避免引入 `--no-build`、新设置文件或无关包升级;除非文件存在,绝不保留 `--settings <file>`。
- .NET 9 及更早显示 `--` 分隔符;.NET 10 原生 MTP 模式明确说明移除它。
- 当有意抑制退出码 8 时,警告广泛抑制可能掩盖坏过滤器导致的意外空运行;将其范围限定到已知零测试的项目/配置。
- 对于退出码 8 问题,展示三种具体形式:`--ignore-exit-code 8`、`TestingPlatformCommandLineArguments` 和 `TESTINGPLATFORM_EXITCODE_IGNORE=8`。
- 对于 xUnit v3 过滤器,给出可直接使用的 `--filter-class`/`--filter-method`/`--filter-trait` 命令并解释 AND 行为;当解析的 xUnit 版本支持时,包含 `--filter-query` 路径语法用于复杂表达式并链接 xUnit 查询过滤器文档。
- 翻译报告器、覆盖率或转储时,在答案中列出每个所需包;看起来正确但缺少所属扩展包的选项是不完整的。
适用边界 · 注意事项
- 项目已在 Microsoft.Testing.Platform 上运行且没有剩余 MTP 行为差异需要解决(例如零测试发现时的退出码 8)。
- 在测试框架之间迁移(如 MSTest 到 xUnit.net)——是完全不同的工作。
- 项目构建 UWP 或打包的 WinUI 测试项目——MTP 尚不支持。
- 解决方案混合 .NET 和非 .NET 测试适配器(如 JavaScript 或 C++ 适配器)——需要 VSTest。
- 升级 MSTest 版本——应使用 `migrate-mstest-v1v2-to-v3` 或 `migrate-mstest-v3-to-v4`。