migrate-xunit-to-mstest
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesexUnit -> MSTest Migration
xUnit 转 MSTest 迁移
Convert xUnit.net v2 or v3 tests to MSTest v4 without changing the target framework or test platform. A successful migration builds, discovers the same tests, and preserves pass/fail results and execution semantics.
无需更改目标框架或测试平台,将xUnit.net v2或v3测试转换为MSTest v4。成功的迁移需保证项目可构建、测试可正常发现,且通过/失败结果和执行语义与原项目一致。
Scope
适用场景
Use this skill only when the project contains xUnit packages or source and the user wants MSTest. If the project already uses MSTest and contains no xUnit tests, report that no framework migration is needed and make no changes.
Do not combine this framework conversion with a target-framework upgrade or VSTest/MTP migration. Complete and verify one migration before starting another.
仅当项目包含xUnit包或xUnit测试代码,且用户希望转换为MSTest时使用该技能。如果项目已使用MSTest且无xUnit测试,需告知无需进行框架迁移,且不做任何修改。
请勿将此框架转换与目标框架升级或VSTest/MTP迁移同时进行。完成并验证一项迁移后,再启动另一项。
Response Mode
响应模式
- Full migration request: inspect the project, make the edits, build, and run tests. Do not stop after giving a plan.
- Focused compile error or API question: inspect the relevant code and apply only that mapping. Do not narrate the entire workflow.
- Unsupported target framework: stop before changing packages. MSTest v4 requires .NET 8+ or .NET Framework 4.6.2+ for test applications; offer a separately approved TFM upgrade or MSTest v3 as the intermediate target.
For detailed mappings and examples, search for constructs actually present in the project and read only the matching sections. Do not load or reproduce the whole reference.
references/mapping-cheatsheet.md- 完整迁移请求:检查项目、完成修改、构建并运行测试。不要仅提供方案就停止操作。
- 聚焦编译错误或API问题:检查相关代码并仅应用对应的映射规则。无需叙述整个工作流程。
- 不支持的目标框架:在修改包之前停止操作。MSTest v4要求测试应用使用.NET 8+或.NET Framework 4.6.2+;可提供单独批准的目标框架(TFM)升级方案,或建议先迁移到MSTest v3作为过渡目标。
如需详细映射规则和示例,请在项目中实际存在的结构对应的章节中搜索,仅阅读匹配部分,无需加载或复制整个参考文档。
references/mapping-cheatsheet.mdFast Path
快速路径
For a routine project migration, converge in four phases: one batched discovery read/search, one edit pass, one , and one concise result. Do not:
dotnet test- list a directory and then reread the same files through another tool
- try unless restore is already known to be current
dotnet test --no-restore - run separate restore, build, and test commands when is sufficient
dotnet test - rerun a passing test command or inspect unchanged files for confirmation
Use an existing CI/test result as the parity baseline when available. Run a new pre-edit baseline only when counts are unavailable and the migration contains data-driven tests, fixtures, skips, custom extensions, shared state, or other behavior whose parity cannot be established from source alone.
对于常规项目迁移,分四个阶段完成:一次批量发现扫描、一次编辑操作、一次命令执行、一次简洁结果反馈。请勿:
dotnet test- 先列出目录,再通过其他工具重新读取相同文件
- 在未确认还原状态为最新的情况下尝试
dotnet test --no-restore - 在已足够的情况下,分别运行还原、构建和测试命令
dotnet test - 重复执行已通过的测试命令,或检查未修改的文件以确认结果
如果有现有CI/测试结果,将其作为 parity 基准。仅当无法获取测试数量,且迁移包含数据驱动测试、夹具、跳过测试、自定义扩展、共享状态或其他无法仅通过代码确认语义一致性的行为时,才在编辑前运行新的基准测试。
Workflow
工作流程
1. Establish the baseline
1. 建立基准
- In one discovery pass, batch-read the test projects plus ,
Directory.Build.props,Directory.Packages.props, and runner configuration, and search the source for the high-risk constructs below.global.json - State the detected source version:
- 2.x and related packages -> xUnit v2
xunit - or
xunit.v3-> xUnit v3xunit.v3.*
- Identify VSTest or MTP from the project and repository configuration. Use only when the platform is ambiguous, and preserve the detected platform.
platform-detection - Record the target frameworks and stop if MSTest v4 does not support them.
- If the Fast Path requires a new baseline, run the existing test command once and record discovered, passed, failed, and skipped counts.
- Inventory high-risk constructs before editing:
- ,
IClassFixture,ICollectionFixture, customCollectionDefinition/FactAttribute/TheoryAttributeDataAttribute - ,
Assert.Throws,ThrowsAny,IsType, event assertionsRecord.Exception - ,
ITestOutputHelper,TestContext.CurrentIAsyncLifetime - ,
CollectionBehavior, shared static or external statexunit.runner.json
- 一次发现扫描中,批量读取测试项目及、
Directory.Build.props、Directory.Packages.props和运行器配置文件,并在代码中搜索以下高风险结构。global.json - 声明检测到的源码版本:
- 2.x及相关包 -> xUnit v2
xunit - 或
xunit.v3-> xUnit v3xunit.v3.*
- 从项目和仓库配置中识别VSTest或MTP。仅当平台不明确时使用,并保留检测到的平台。
platform-detection - 记录目标框架,如果MSTest v4不支持则停止操作。
- 如果快速路径需要新基准,运行一次现有测试命令并记录已发现、通过、失败和跳过的测试数量。
- 在编辑前盘点高风险结构:
- 、
IClassFixture、ICollectionFixture、自定义CollectionDefinition/FactAttribute/TheoryAttributeDataAttribute - 、
Assert.Throws、ThrowsAny、IsType、事件断言Record.Exception - 、
ITestOutputHelper、TestContext.CurrentIAsyncLifetime - 、
CollectionBehavior、共享静态或外部状态xunit.runner.json
2. Replace packages without switching runners
2. 替换包但不切换运行器
Remove xUnit packages from project files and central package files. This includes , , , , and xUnit-specific companion packages that are being replaced.
xunit*xunit.v3.*xunit.runner.visualstudioYTest.MTP.XUnit2Default to the MSTest v4 metapackage for an incremental conversion:
xml
<PackageReference Include="MSTest" Version="4.1.0" />This keeps VSTest available through the metapackage's compatible dependency. Remove a stale explicit reference or update it to the minimum required by the chosen MSTest version (MSTest 4.1.0 requires 18.0.1+); otherwise restore fails with . Use only when the project already uses it elsewhere or the user explicitly requests it. defaults to MTP, so add when preserving VSTest.
Microsoft.NET.Test.SdkMicrosoft.NET.Test.SdkNU1605MSTest.SdkMSTest.Sdk<UseVSTest>true</UseVSTest>Do not change . Remove only after porting its relevant settings.
TargetFrameworkxunit.runner.json从项目文件和中心包文件中移除xUnit包,包括、、、以及正在被替换的xUnit专属配套包。
xunit*xunit.v3.*xunit.runner.visualstudioYTest.MTP.XUnit2默认使用MSTest v4元包进行增量转换:
xml
<PackageReference Include="MSTest" Version="4.1.0" />该元包通过兼容的依赖保持VSTest可用。移除过时的显式引用,或将其更新为所选MSTest版本要求的最低版本(MSTest 4.1.0要求18.0.1+);否则还原操作会因失败。仅当项目已在其他地方使用或用户明确要求时,才使用。默认使用MTP,因此在保留VSTest时需添加。
Microsoft.NET.Test.SdkMicrosoft.NET.Test.SdkNU1605MSTest.SdkMSTest.SdkMSTest.Sdk<UseVSTest>true</UseVSTest>请勿修改。仅在移植相关设置后移除。
TargetFrameworkxunit.runner.json3. Perform the mechanical conversion
3. 执行机械转换
Apply the common rewrites first:
| xUnit | MSTest |
|---|---|
| no class attribute | |
| |
| |
| |
| |
| |
| |
other | |
| |
| |
| |
Remove and . Add for the metapackage option; supplies it as an implicit global using.
using Xunit;using Xunit.Abstractions;using Microsoft.VisualStudio.TestTools.UnitTesting;MSTest.SdkPreserve existing class inheritance. Do not mechanically seal classes.
先应用通用重写规则:
| xUnit | MSTest |
|---|---|
| 无类特性 | |
| |
| |
| |
| |
| |
| |
其他 | |
| |
| |
| |
移除和。如果使用元包选项,添加;会将其作为隐式全局引用提供。
using Xunit;using Xunit.Abstractions;using Microsoft.VisualStudio.TestTools.UnitTesting;MSTest.Sdk保留现有类继承关系,不要机械地将类设为密封类。
4. Resolve semantic mappings
4. 解决语义映射
Load the mapping cheatsheet for every high-risk construct found in Step 1. These rules are mandatory:
- xUnit is exact-type and maps to MSTest
Assert.Throws<T>.Assert.ThrowsExactly<T> - xUnit permits derived types and maps to MSTest
Assert.ThrowsAny<T>.Assert.Throws<T> - xUnit is exact-type and maps to
Assert.IsType<T>;Assert.IsExactInstanceOfType<T>maps toAssert.IsAssignableFrom<T>.Assert.IsInstanceOfType<T> - xUnit on sequences compares elements. Use
Assert.Equalon MSTest 4.3+ orAssert.AreSequenceEqualwith materialized lists on earlier v4; never replace sequence equality with reference-basedCollectionAssert.AreEqual.Assert.AreEqual - and
[Ignore]are modifiers; keep[Timeout]so the test is discovered.[TestMethod] - values must exactly match parameter types.
[DataRow] - maps to an injected MSTest
TestContext.Current.CancellationToken; never replace it withTestContext.CancellationTokenor a newCancellationToken.None.CancellationTokenSource - is a reserved VSTest property. Map
Ownerto[Trait("Owner", value)], not[Owner(value)].[TestProperty("Owner", value)] - Assertions with no MSTest equivalent (,
Assert.Collection,Assert.All,Assert.Equivalent, event assertions) require an explicit manual rewrite. Never delete an assertion without replacing its verification.Record.Exception
Apply the mechanical and semantic rewrites in one edit pass when the inventory makes the required mappings clear. Do not run an intermediate build by default; use compiler errors from final verification to drive only unresolved conversions.
针对步骤1中发现的每个高风险结构,加载映射速查表。以下规则为强制要求:
- xUnit的为精确类型匹配,对应MSTest的
Assert.Throws<T>。Assert.ThrowsExactly<T> - xUnit的允许派生类型,对应MSTest的
Assert.ThrowsAny<T>。Assert.Throws<T> - xUnit的为精确类型匹配,对应
Assert.IsType<T>;Assert.IsExactInstanceOfType<T>对应Assert.IsAssignableFrom<T>。Assert.IsInstanceOfType<T> - xUnit中对序列使用会比较元素。在MSTest 4.3+上使用
Assert.Equal,在更早的v4版本上使用Assert.AreSequenceEqual并传入物化列表;绝不能将序列相等替换为基于引用的CollectionAssert.AreEqual。Assert.AreEqual - 和
[Ignore]是修饰符;需保留[Timeout]以确保测试可被发现。[TestMethod] - 的值必须与参数类型完全匹配。
[DataRow] - 对应注入的MSTest
TestContext.Current.CancellationToken;绝不能将其替换为TestContext.CancellationToken或新的CancellationToken.None。CancellationTokenSource - 是VSTest的保留属性。将
Owner映射为[Trait("Owner", value)],而非[Owner(value)]。[TestProperty("Owner", value)] - 无MSTest等效实现的断言(如、
Assert.Collection、Assert.All、Assert.Equivalent、事件断言)需要显式手动重写。绝不能在未替换验证逻辑的情况下删除断言。Record.Exception
当盘点结果明确所需映射规则时,在一次编辑操作中完成机械转换和语义转换。默认情况下无需运行中间构建;仅通过最终验证产生的编译器错误来处理未解决的转换问题。
5. Preserve lifecycle, fixture scope, and parallelization
5. 保留生命周期、夹具范围和并行机制
- Keep constructor setup and /
IDisposablewhen valid. MapIAsyncDisposabletoIAsyncLifetime/[TestInitialize].[TestCleanup] - Map to class-scoped initialization and cleanup.
IClassFixture<T> - For , preserve both sharing and serialization. Prefer a static
ICollectionFixture<T>helper used by each member class; addLazy<T>only when the source collection disabled parallelization. Use assembly initialization only when the fixture is genuinely assembly-wide.[DoNotParallelize] - Replace with injected or property-based MSTest
ITestOutputHelper.TestContext
xUnit runs classes in parallel by default; MSTest runs them serially. Unless the source disabled parallelism, preserve xUnit behavior with:
csharp
[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)]Never use to emulate xUnit. Before applying a fixture-scope or parallelization decision, state what the source shared or serialized and how the target preserves it.
ExecutionScope.MethodLevel- 保留有效的构造函数初始化和/
IDisposable。将IAsyncDisposable映射为IAsyncLifetime/[TestInitialize]。[TestCleanup] - 将映射为类级别的初始化和清理。
IClassFixture<T> - 对于,保留共享和序列化特性。优先使用每个成员类都能调用的静态
ICollectionFixture<T>助手;仅当原集合禁用并行时添加Lazy<T>。仅当夹具真正是程序集级别的时,才使用程序集初始化。[DoNotParallelize] - 将替换为注入式或基于属性的MSTest
ITestOutputHelper。TestContext
xUnit默认并行运行类;MSTest默认串行运行类。除非原代码禁用了并行,否则需通过以下代码保留xUnit的行为:
csharp
[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)]绝不能使用来模拟xUnit的行为。在确定夹具范围或并行机制的处理方式前,需说明原代码的共享或序列化逻辑,以及目标代码如何保留这些逻辑。
ExecutionScope.MethodLevel6. Verify parity
6. 验证一致性
- Run tests once with the same platform, filter, and configuration used for the baseline. builds by default; run a separate build only when needed to isolate a compilation failure.
dotnet test - Compare discovered, passed, failed, and skipped counts.
- Investigate every difference before declaring completion:
- missing cases -> discovery attributes, , or
DynamicDataliteral typesDataRow - changed exception behavior -> exact-vs-derived assertion mapping
- shared-state failures or large duration changes -> fixture scope and parallelization
- silently skipped tests -> missing or incorrect runtime-skip conversion
[TestMethod]
- missing cases -> discovery attributes,
- Confirm no xUnit package, namespace, attribute, runner configuration, or fixture interface remains unless explicitly documented for manual follow-up.
- 使用与基准测试相同的平台、过滤器和配置运行一次测试。默认会执行构建;仅在需要隔离编译失败时才单独运行构建。
dotnet test - 对比已发现、通过、失败和跳过的测试数量。
- 在宣布完成前调查所有差异:
- 缺失测试用例 -> 发现特性、或
DynamicData字面量类型问题DataRow - 异常行为变化 -> 精确类型与派生类型的断言映射问题
- 共享状态失败或耗时大幅变化 -> 夹具范围和并行机制问题
- 静默跳过测试 -> 缺少或运行时跳过逻辑转换错误
[TestMethod]
- 缺失测试用例 -> 发现特性、
- 确认除明确记录需手动跟进的内容外,无xUnit包、命名空间、特性、运行器配置或夹具接口残留。
Completion Criteria
完成标准
- Current xUnit version and test platform were identified
- xUnit packages and source constructs were converted
- Target framework and test platform stayed unchanged
- Fixture scope and parallelization decisions are explicit
- Build succeeds
- Test discovery and result counts match the baseline
- Any unsupported custom extension point is called out rather than approximated
- 已识别当前xUnit版本和测试平台
- 已完成xUnit包和代码结构的转换
- 目标框架和测试平台未发生变化
- 夹具范围和并行机制的决策已明确记录
- 构建成功
- 测试发现数量和结果与基准一致
- 任何不支持的自定义扩展点已被明确标注,而非近似处理
Follow-up
后续操作
Run separately if the user also wants MTP. Use only after parity is established to polish the converted MSTest code.
migrate-vstest-to-mtpwriting-mstest-tests如果用户同时希望转换为MTP,请单独运行。仅在验证一致性后,才可使用来优化转换后的MSTest代码。
migrate-vstest-to-mtpwriting-mstest-tests