migrate-xunit-to-mstest

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

xUnit -> 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
references/mapping-cheatsheet.md
for constructs actually present in the project and read only the matching sections. Do not load or reproduce the whole reference.
  • 完整迁移请求:检查项目、完成修改、构建并运行测试。不要仅提供方案就停止操作。
  • 聚焦编译错误或API问题:检查相关代码并仅应用对应的映射规则。无需叙述整个工作流程。
  • 不支持的目标框架:在修改包之前停止操作。MSTest v4要求测试应用使用.NET 8+或.NET Framework 4.6.2+;可提供单独批准的目标框架(TFM)升级方案,或建议先迁移到MSTest v3作为过渡目标。
如需详细映射规则和示例,请在项目中实际存在的结构对应的
references/mapping-cheatsheet.md
章节中搜索,仅阅读匹配部分,无需加载或复制整个参考文档。

Fast Path

快速路径

For a routine project migration, converge in four phases: one batched discovery read/search, one edit pass, one
dotnet test
, and one concise result. Do not:
  • list a directory and then reread the same files through another tool
  • try
    dotnet test --no-restore
    unless restore is already known to be current
  • run separate restore, build, and test commands when
    dotnet test
    is sufficient
  • 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. 建立基准

  1. In one discovery pass, batch-read the test projects plus
    Directory.Build.props
    ,
    Directory.Packages.props
    ,
    global.json
    , and runner configuration, and search the source for the high-risk constructs below.
  2. State the detected source version:
    • xunit
      2.x and related packages -> xUnit v2
    • xunit.v3
      or
      xunit.v3.*
      -> xUnit v3
  3. Identify VSTest or MTP from the project and repository configuration. Use
    platform-detection
    only when the platform is ambiguous, and preserve the detected platform.
  4. Record the target frameworks and stop if MSTest v4 does not support them.
  5. If the Fast Path requires a new baseline, run the existing test command once and record discovered, passed, failed, and skipped counts.
  6. Inventory high-risk constructs before editing:
    • IClassFixture
      ,
      ICollectionFixture
      ,
      CollectionDefinition
      , custom
      FactAttribute
      /
      TheoryAttribute
      /
      DataAttribute
    • Assert.Throws
      ,
      ThrowsAny
      ,
      IsType
      ,
      Record.Exception
      , event assertions
    • ITestOutputHelper
      ,
      TestContext.Current
      ,
      IAsyncLifetime
    • CollectionBehavior
      ,
      xunit.runner.json
      , shared static or external state
  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. Replace packages without switching runners

2. 替换包但不切换运行器

Remove xUnit packages from project files and central package files. This includes
xunit*
,
xunit.v3.*
,
xunit.runner.visualstudio
,
YTest.MTP.XUnit2
, and xUnit-specific companion packages that are being replaced.
Default 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
Microsoft.NET.Test.Sdk
dependency. Remove a stale explicit
Microsoft.NET.Test.Sdk
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
NU1605
. Use
MSTest.Sdk
only when the project already uses it elsewhere or the user explicitly requests it.
MSTest.Sdk
defaults to MTP, so add
<UseVSTest>true</UseVSTest>
when preserving VSTest.
Do not change
TargetFramework
. Remove
xunit.runner.json
only after porting its relevant settings.
从项目文件和中心包文件中移除xUnit包,包括
xunit*
xunit.v3.*
xunit.runner.visualstudio
YTest.MTP.XUnit2
以及正在被替换的xUnit专属配套包。
默认使用MSTest v4元包进行增量转换:
xml
<PackageReference Include="MSTest" Version="4.1.0" />
该元包通过兼容的
Microsoft.NET.Test.Sdk
依赖保持VSTest可用。移除过时的显式
Microsoft.NET.Test.Sdk
引用,或将其更新为所选MSTest版本要求的最低版本(MSTest 4.1.0要求18.0.1+);否则还原操作会因
NU1605
失败。仅当项目已在其他地方使用
MSTest.Sdk
或用户明确要求时,才使用
MSTest.Sdk
MSTest.Sdk
默认使用MTP,因此在保留VSTest时需添加
<UseVSTest>true</UseVSTest>
请勿修改
TargetFramework
。仅在移植相关设置后移除
xunit.runner.json

3. Perform the mechanical conversion

3. 执行机械转换

Apply the common rewrites first:
xUnitMSTest
no class attribute
[TestClass]
[Fact]
[TestMethod]
[Theory]
+
[InlineData]
[TestMethod]
+
[DataRow]
[MemberData]
[DynamicData]
[Fact(Skip = "...")]
[TestMethod]
+
[Ignore("...")]
[Trait("Category", value)]
[TestCategory(value)]
[Trait("Owner", value)]
[Owner(value)]
other
[Trait(key, value)]
[TestProperty(key, value)]
Assert.Equal
/
NotEqual
Assert.AreEqual
/
AreNotEqual
Assert.True
/
False
Assert.IsTrue
/
IsFalse
Assert.Null
/
NotNull
Assert.IsNull
/
IsNotNull
Remove
using Xunit;
and
using Xunit.Abstractions;
. Add
using Microsoft.VisualStudio.TestTools.UnitTesting;
for the metapackage option;
MSTest.Sdk
supplies it as an implicit global using.
Preserve existing class inheritance. Do not mechanically seal classes.
先应用通用重写规则:
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
/
NotEqual
Assert.AreEqual
/
AreNotEqual
Assert.True
/
False
Assert.IsTrue
/
IsFalse
Assert.Null
/
NotNull
Assert.IsNull
/
IsNotNull
移除
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
    Assert.Throws<T>
    is exact-type and maps to MSTest
    Assert.ThrowsExactly<T>
    .
  • xUnit
    Assert.ThrowsAny<T>
    permits derived types and maps to MSTest
    Assert.Throws<T>
    .
  • xUnit
    Assert.IsType<T>
    is exact-type and maps to
    Assert.IsExactInstanceOfType<T>
    ;
    Assert.IsAssignableFrom<T>
    maps to
    Assert.IsInstanceOfType<T>
    .
  • xUnit
    Assert.Equal
    on sequences compares elements. Use
    Assert.AreSequenceEqual
    on MSTest 4.3+ or
    CollectionAssert.AreEqual
    with materialized lists on earlier v4; never replace sequence equality with reference-based
    Assert.AreEqual
    .
  • [Ignore]
    and
    [Timeout]
    are modifiers; keep
    [TestMethod]
    so the test is discovered.
  • [DataRow]
    values must exactly match parameter types.
  • TestContext.Current.CancellationToken
    maps to an injected MSTest
    TestContext.CancellationToken
    ; never replace it with
    CancellationToken.None
    or a new
    CancellationTokenSource
    .
  • Owner
    is a reserved VSTest property. Map
    [Trait("Owner", value)]
    to
    [Owner(value)]
    , not
    [TestProperty("Owner", value)]
    .
  • Assertions with no MSTest equivalent (
    Assert.Collection
    ,
    Assert.All
    ,
    Assert.Equivalent
    ,
    Record.Exception
    , event assertions) require an explicit manual rewrite. Never delete an assertion without replacing its verification.
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的
    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中对序列使用
    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. Preserve lifecycle, fixture scope, and parallelization

5. 保留生命周期、夹具范围和并行机制

  • Keep constructor setup and
    IDisposable
    /
    IAsyncDisposable
    when valid. Map
    IAsyncLifetime
    to
    [TestInitialize]
    /
    [TestCleanup]
    .
  • Map
    IClassFixture<T>
    to class-scoped initialization and cleanup.
  • For
    ICollectionFixture<T>
    , preserve both sharing and serialization. Prefer a static
    Lazy<T>
    helper used by each member class; add
    [DoNotParallelize]
    only when the source collection disabled parallelization. Use assembly initialization only when the fixture is genuinely assembly-wide.
  • Replace
    ITestOutputHelper
    with injected or property-based MSTest
    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
ExecutionScope.MethodLevel
to emulate xUnit. Before applying a fixture-scope or parallelization decision, state what the source shared or serialized and how the target preserves it.
  • 保留有效的构造函数初始化和
    IDisposable
    /
    IAsyncDisposable
    。将
    IAsyncLifetime
    映射为
    [TestInitialize]
    /
    [TestCleanup]
  • IClassFixture<T>
    映射为类级别的初始化和清理。
  • 对于
    ICollectionFixture<T>
    ,保留共享和序列化特性。优先使用每个成员类都能调用的静态
    Lazy<T>
    助手;仅当原集合禁用并行时添加
    [DoNotParallelize]
    。仅当夹具真正是程序集级别的时,才使用程序集初始化。
  • ITestOutputHelper
    替换为注入式或基于属性的MSTest
    TestContext
xUnit默认并行运行类;MSTest默认串行运行类。除非原代码禁用了并行,否则需通过以下代码保留xUnit的行为:
csharp
[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)]
绝不能使用
ExecutionScope.MethodLevel
来模拟xUnit的行为。在确定夹具范围或并行机制的处理方式前,需说明原代码的共享或序列化逻辑,以及目标代码如何保留这些逻辑。

6. Verify parity

6. 验证一致性

  1. Run tests once with the same platform, filter, and configuration used for the baseline.
    dotnet test
    builds by default; run a separate build only when needed to isolate a compilation failure.
  2. Compare discovered, passed, failed, and skipped counts.
  3. Investigate every difference before declaring completion:
    • missing cases -> discovery attributes,
      DynamicData
      , or
      DataRow
      literal types
    • changed exception behavior -> exact-vs-derived assertion mapping
    • shared-state failures or large duration changes -> fixture scope and parallelization
    • silently skipped tests -> missing
      [TestMethod]
      or incorrect runtime-skip conversion
  4. Confirm no xUnit package, namespace, attribute, runner configuration, or fixture interface remains unless explicitly documented for manual follow-up.
  1. 使用与基准测试相同的平台、过滤器和配置运行一次测试。
    dotnet test
    默认会执行构建;仅在需要隔离编译失败时才单独运行构建。
  2. 对比已发现、通过、失败和跳过的测试数量。
  3. 在宣布完成前调查所有差异:
    • 缺失测试用例 -> 发现特性、
      DynamicData
      DataRow
      字面量类型问题
    • 异常行为变化 -> 精确类型与派生类型的断言映射问题
    • 共享状态失败或耗时大幅变化 -> 夹具范围和并行机制问题
    • 静默跳过测试 -> 缺少
      [TestMethod]
      或运行时跳过逻辑转换错误
  4. 确认除明确记录需手动跟进的内容外,无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
migrate-vstest-to-mtp
separately if the user also wants MTP. Use
writing-mstest-tests
only after parity is established to polish the converted MSTest code.
如果用户同时希望转换为MTP,请单独运行
migrate-vstest-to-mtp
。仅在验证一致性后,才可使用
writing-mstest-tests
来优化转换后的MSTest代码。