mcpField
M
migrate-mstest-v3-to-v4

migrate-mstest-v3-to-v4

github.com/dotnet测试与质量5.8k3.4k 次安装1 天前更新
代码仓库

关于 migrate-mstest-v3-to-v4

用于将 MSTest v3 项目迁移到 v4 的技能,涵盖包、源码、运行时和测试发现变更,并根据项目状态与需求提供指导。

主要功能

  1. 01检查 MSTest 版本与目标框架
  2. 02指导更新包至 v4
  3. 03将源码变更对应到代码修复
  4. 04涵盖运行时与测试发现问题

适用场景

  1. 01将 MSTest v3 项目迁移到 v4
  2. 02修复升级后的编译错误
  3. 03诊断测试发现或 CI 失败
  4. 04评估运行时行为变化

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dotnet/skills --skill migrate-mstest-v3-to-v4

安装后在 Claude Code 中输入/migrate-mstest-v3-to-v4确认已加载。

详细介绍

MSTest v3 → v4 迁移

将测试项目从 MSTest v3 迁移到 MSTest v4。迁移后的项目使用 MSTest v4,能够顺利构建、通过测试,并处理所有源代码不兼容变更和行为变更。MSTest v4 与 MSTest v3 不具备二进制兼容性——任何基于 v3 编译的库都必须针对 v4 重新编译。

首要操作

在搜索网络或凭记忆作答之前,先检查提供的项目和源代码。将请求归类为针对性源代码修复、运行时行为变更、CI 发现问题、兼容性问题或完整迁移,然后按照下方对应的行处理。顺利编译并不代表无需使用此技能:发现失败、TestContext 生命周期异常和测试历史变更都属于运行时迁移失败。

适用场景

  • 将 MSTest.TestFramework、MSTest.TestAdapter 或 MSTest 元包从 3.x 升级到 4.x
  • 将 MSTest.Sdk 从 3.x 升级到 4.x
  • 更新到 MSTest v4 包后修复构建错误
  • 解决升级到 MSTest v4 后测试执行行为发生的变更
  • 更新自定义 TestMethodAttribute 或 ConditionBaseAttribute 实现以适配 v4

不适用场景

  • 项目已经使用 MSTest v4,且能够顺利构建——迁移已完成
  • 从 MSTest v1 或 v2 升级——先使用 migrate-mstest-v1v2-to-v3,然后再返回此处
  • 项目未使用 MSTest
  • 在不同测试框架之间迁移(例如从 MSTest 迁移到 xUnit 或 NUnit)

输入

输入必需描述
项目或解决方案路径否包含 MSTest 测试项目的 .csproj、.sln 或 .slnx 入口点。请自行发现:遍历工作目录;仅当未找到或选项确实模糊不清时才询问
构建命令否构建方式(例如 dotnet build、仓库构建脚本)。如果未提供,请自动检测
测试命令否测试运行方式(例如 dotnet test)。如果未提供,请自动检测

会改变处理结果的判断

检测到的请求或状态必需操作
当前工作区中提供了文件在其中搜索,并打开实际返回的路径。技能目录不是项目目录。如果某个工具无法读取有效路径,请改用其他可用的读取器或编辑器重试;不要向用户询问自己能够发现的路径。
用户要求修改提供的文件:“修复我的项目/文件”“请更新此源代码”“进行这些更改”或“然后构建并运行”修改所有受影响的位置。针对实际使用的包版本运行范围最小且有意义的构建/测试命令;不能因为启用了技能就只提供建议。
用户询问“我应该预期什么?”“如何修复这些变更?”,寻求兼容性建议或要求制定计划根据实际项目状态直接回答,即使源代码可见也是如此。针对单一问题的回答应聚焦于该问题;仅包含会影响决策的相邻风险。
完整迁移中目标框架不受支持先更新目标框架(TFM),再更新 MSTest 包,最后修复源代码中的问题。不要将此顺序埋在发行说明清单中。
存在自定义 TestMethodAttribute 子类将 ExecuteAsync、CallerInfo 传播、显示名称处理以及子类的重试/结果语义视为一组相互关联的迁移内容。修复实际类,而不是占位示例。
存在 MSTest.Sdk v4 源代码/API 错误(ManagedType、TestTimeout、Contains)给出准确的源代码替换方案,然后补充相邻的运行器警告:MTP 模式不再提供 Microsoft.NET.Test.Sdk;仅当仍需要 VSTest 发现时才添加该包。
MSTest.Sdk v4 与 vstest.console 同时使用这是 v4 的变更:MTP 模式不再引入 Microsoft.NET.Test.Sdk。保留 MTP 并添加该包以支持过渡期间的 VSTest 发现,启用 UseVSTest,或将 CI 迁移到 dotnet test;请说明所选方案保留的是哪个运行器。

响应指南

  • 始终先确认当前版本:在推荐任何迁移步骤前,明确说明在项目中检测到的当前 MSTest 版本(例如:“您的项目使用 MSTest v3(3.8.0)”)。这能确认您已阅读项目文件,并为迁移建议提供依据。
  • 确认目标版本,不要想当然:用户询问“最新版本”时,查询项目已配置的包源,并选择执行时可用的最新稳定版 MSTest v4。未经检查,绝不能直接照搬此技能中的示例版本。所有 MSTest 包都必须使用同一个已确认的版本。
  • 针对性修复请求(用户升级后遇到特定编译错误):只处理步骤 3 中相关的重大变更。只有在用户要求交付源代码变更时才修改代码;“这些问题该如何修复?”属于回答请求。务必提供使用用户实际类型和方法名的具体修复代码。如果测试夹具仍引用 v3,不要声称 v3 构建成功就能验证与 v4 的兼容性;应在用户要求时更新包,或说明验证范围。对于自定义 TestMethodAttribute 子类,展示完整的修复后类,包括将 CallerInfo 传递给基类构造函数。提及可能及早发现此问题的相关分析器(例如,针对 ExpectedException 的 MSTEST0006)。如果项目使用 MSTest.Sdk,还要说明 v4 MTP 模式不再提供 Microsoft.NET.Test.Sdk,以及这是否会影响当前可见的运行器。不要逐步介绍完整迁移流程。
  • “会有哪些变化”类问题(用户在升级前询问重大变更):展示步骤 3 快速查阅表中的所有重大变更,而不只是当前代码中可见的变更。每项都用一行概述修复方法。还要提及步骤 4 中的关键行为变更(尤其是 TestCase.Id 历史记录影响和 TreatDiscoveryWarningsAsErrors 默认值)。如果可以查看项目代码,指出哪些变更会直接影响项目。
  • 完整迁移请求(用户要求完成迁移):遵循下方的完整工作流程。
  • 行为或运行时问题报告(用户描述测试执行差异,但没有构建错误):将描述的症状对应到步骤 4 的行为变更表。提供针对症状的建议。提醒用户留意其他行为变更。除非用户同时遇到构建错误,否则不要逐项讲解源代码层面的重大变更。
  • CI 或测试发现问题(升级后未发现测试、vstest.console 停止工作或 CI 管道失败):重点说明 4.5(MSTest.Sdk v4 默认 MTP 模式不再包含 Microsoft.NET.Test.Sdk——但 vstest.console 仍需要它)和 4.4(TreatDiscoveryWarningsAsErrors)。清楚解释根本原因,并提供全部三种处理方式:添加 Microsoft.NET.Test.Sdk,同时保留 MTP;设置 UseVSTest 以切换项目运行器;或将 CI 切换为使用 MTP 的 dotnet test。不要逐步介绍完整迁移流程。
  • 解释类问题(用户询问“这是已知变更吗?”或“还有什么需要注意的?”):解释相关变更并提供建议。提及用户接下来可能遇到的相关变更。不要要求执行完整迁移流程。
  • 结果证明:实现工作结束时,报告检测到的 v3 版本、已确认的 v4 版本、运行器选择、修改的文件以及实际的构建和测试数量。绝不要根据推断声称构建成功、兼容 VSTest、测试发现正常或测试通过。

工作流程

**提交策略:**除非用户要求,否则不要创建提交。确保包、源代码和行为方面的变更在差异中逻辑上分开,但务必完成并验证用户要求的迁移。

步骤 1:评估项目
  1. 检查 .csproj、Directory.Build.props 或 Directory.Packages.props 中对 MSTest、MSTest.TestFramework、MSTest.TestAdapter 或 MSTest.Sdk 的包引用,确定当前 MSTest 版本。
  2. 确认项目使用的是 MSTest v3(3.x)。如果使用的是 v1 或 v2,请先使用 migrate-mstest-v1v2-to-v3。
  3. 检查目标框架 -- MSTest v4 不再支持 .NET Core 3.1 至 .NET 7。支持的目标框架包括:net8.0、net9.0、net462(.NET Framework 4.6.2+)、uap10.0.16299(UWP)、net9.0-windows10.0.17763.0(现代 UWP)和 net8.0-windows10.0.18362.0(WinUI)。
  4. 检查是否有自定义的 TestMethodAttribute 子类 -- 这些类在 v4 中需要修改。
  5. 检查是否使用了 ExpectedExceptionAttribute -- 该特性已在 v4 中移除(自 v3 起已弃用,并有分析器 MSTEST0006 提示)。
  6. 检查是否使用了 Assert.ThrowsException(已弃用)-- 该方法在 v4 中已移除。
  7. 执行干净构建,记录现有错误和警告作为基线。
步骤 2:将包更新到 MSTest v4

首先从配置的包源获取最新稳定版 v4。请在元包、独立包、MSTest.Sdk 和中央包管理中统一固定为该确切版本。

  • 对于 MSTest 元包,将其 PackageReference 更新为获取到的确切版本。
  • 对于独立包,将 MSTest.TestFramework 和 MSTest.TestAdapter 更新为相同版本。
  • 对于 MSTest.Sdk,将项目或 global.json 中固定的 SDK 版本更新为相同版本。

运行 dotnet restore,然后运行 dotnet build。收集所有错误,供步骤 3 处理。

步骤 3:解决源代码中断性变更

系统地处理编译错误。使用此速查表找出所有适用的变更,然后逐一修复:

代码中的错误 / 模式中断性变更修复方法
自定义 TestMethodAttribute 重写了 ExecuteExecute 已移除改为 ExecuteAsync,返回 Task<TestResult[]>(3.1)
[TestMethod("name")] 或自定义特性的构造函数已添加 CallerInfo 参数使用命名参数 DisplayName = "name";在子类中传递 CallerInfo(3.2)
ClassCleanupBehavior.EndOfClass枚举已移除移除参数:只保留 [ClassCleanup](3.3)
TestContext.Properties.Contains("key")Properties 是 IDictionary<string, object>改为 ContainsKey("key")(3.4)
[Timeout(TestTimeout.Infinite)]TestTimeout 枚举已移除替换为 [Timeout(int.MaxValue)](3.5)
TestContext.ManagedType属性已移除改用 FullyQualifiedTestClassName(3.6)
Assert.AreEqual(a, b, "msg {0}", arg)消息和参数重载已移除使用字符串插值:$"msg {arg}"(3.7)
Assert.ThrowsException<T>(...)已重命名替换为 Assert.ThrowsExactly<T>(...) 或 Assert.Throws<T>(...)(3.7)
Assert.IsInstanceOfType<T>(obj, out var t)输出参数已移除使用 var t = Assert.IsInstanceOfType<T>(obj)(3.7)
[ExpectedException(typeof(T))]特性已移除将断言移至测试主体中:Assert.ThrowsExactly<T>(() => ...)(3.8)
项目目标为 net5.0、net6.0 或 net7.0TFM 已移除改为 net8.0 或 net9.0(3.9)

重要:开始修复前,请扫描整个项目,查找上述所有模式。同一个项目中往往会同时存在多项中断性变更。

3.1 TestMethodAttribute.Execute -> ExecuteAsync

如果有自定义的 TestMethodAttribute 子类重写了 Execute,请改为 ExecuteAsync。这样修改是因为 v3 的同步 Execute API 会在测试代码内部使用 async/await 时造成死锁 -- 同步包装器会阻塞线程,而异步操作需要该线程才能完成。

// Before (v3)
public sealed class MyTestMethodAttribute : TestMethodAttribute
{
    public override TestResult[] Execute(ITestMethod testMethod)
    {
        // custom logic
        return result;
    }
}

// After (v4) -- Option A: wrap synchronous logic with Task.FromResult
public sealed class MyTestMethodAttribute : TestMethodAttribute
{
    public override Task<TestResult[]> ExecuteAsync(ITestMethod testMethod)
    {
        // custom logic (synchronous)
        return Task.FromResult(result);
    }
}

// After (v4) -- Option B: make properly async
public sealed class MyTestMethodAttribute : TestMethodAttribute
{
    public override async Task<TestResult[]> ExecuteAsync(ITestMethod testMethod)
    {
        // custom async logic
        return await base.ExecuteAsync(testMethod);
    }
}

如果重写方法中的逻辑完全同步,请使用 Task.FromResult。如果调用 base.ExecuteAsync 或其他异步方法,请使用 async/await。

3.2 TestMethodAttribute 调用方信息构造函数

TestMethodAttribute 现在在其构造函数中使用 [CallerFilePath] 和 [CallerLineNumber] 参数。

如果你继承了 TestMethodAttribute,请将调用方信息传递给基类:

public class MyTestMethodAttribute : TestMethodAttribute
{
    public MyTestMethodAttribute(
        [CallerFilePath] string callerFilePath = "",
        [CallerLineNumber] int callerLineNumber = -1)
        : base(callerFilePath, callerLineNumber)
    {
    }
}

如果子类有自己的显示名称构造函数,请勿将该字符串传递给 v4 基类构造函数。只传递调用方信息,并设置 DisplayName 属性:

public sealed class NamedTestMethodAttribute : TestMethodAttribute
{
    public NamedTestMethodAttribute(
        string displayName,
        [CallerFilePath] string callerFilePath = "",
        [CallerLineNumber] int callerLineNumber = -1)
        : base(callerFilePath, callerLineNumber)
    {
        DisplayName = displayName;
    }
}

如果你使用 [TestMethodAttribute("Custom display name")],请改用命名参数语法:

// Before (v3)
[TestMethodAttribute("Custom display name")]

// After (v4)
[TestMethodAttribute(DisplayName = "Custom display name")]
3.3 移除 ClassCleanupBehavior 枚举

ClassCleanupBehavior 枚举已移除。在 v3 中,此枚举用于控制类清理是在类结束时(EndOfClass)还是程序集结束时(EndOfAssembly)运行。在 v4 中,类清理始终在类结束时运行。请移除枚举参数:

// Before (v3)
[ClassCleanup(ClassCleanupBehavior.EndOfClass)]
public static void ClassCleanup(TestContext testContext) { }

// After (v4)
[ClassCleanup]
public static void ClassCleanup(TestContext testContext) { }

如果你之前使用了 ClassCleanupBehavior.EndOfAssembly,请将该清理逻辑移至 [AssemblyCleanup] 方法。

3.4 TestContext.Properties 类型变更

TestContext.Properties 的类型已从 IDictionary 变更为 IDictionary<string, object>。请将所有 Contains 调用更新为 ContainsKey:

// Before (v3)
testContext.Properties.Contains("key");

// After (v4)
testContext.Properties.ContainsKey("key");
3.5 移除 TestTimeout 枚举

TestTimeout 枚举(仅包含 TestTimeout.Infinite)已移除。请替换为 int.MaxValue:

// Before (v3)
[Timeout(TestTimeout.Infinite)]

// After (v4)
[Timeout(int.MaxValue)]
3.6 移除 TestContext.ManagedType

TestContext.ManagedType 属性已移除。请改用 TestContext.FullyQualifiedTestClassName。

3.7 Assert API 签名变更
  • 移除 message + params:以前同时接受 message 和 object[] 参数的 Assert 方法,现在只接受 message。请使用字符串插值,而不是格式字符串:
// Before (v3)
Assert.AreEqual(expected, actual, "Expected {0} but got {1}", expected, actual);

// After (v4)
Assert.AreEqual(expected, actual, $"Expected {expected} but got {actual}");
  • 重命名 Assert.ThrowsException:Assert.ThrowsException API 已重命名。请使用 Assert.ThrowsExactly(严格匹配类型)或 Assert.Throws(接受派生异常类型):
// Before (v3)
Assert.ThrowsException<InvalidOperationException>(() => DoSomething());

// After (v4) -- exact type match (same behavior as old ThrowsException)
Assert.ThrowsExactly<InvalidOperationException>(() => DoSomething());

// After (v4) -- also catches derived exception types
Assert.Throws<InvalidOperationException>(() => DoSomething());
  • 变更 Assert.IsInstanceOfType 的 out 参数:Assert.IsInstanceOfType<T>(x, out var t) 改为 var t = Assert.IsInstanceOfType<T>(x):
// Before (v3)
Assert.IsInstanceOfType<MyType>(obj, out var typed);

// After (v4)
var typed = Assert.IsInstanceOfType<MyType>(obj);

请对每一处都应用此赋值改写,同时保留具体断言类型以及该类型化变量后续的所有用法。如果有源代码,请展示或编辑实际方法,而不是替换成泛型 MyType 示例,然后验证项目能够编译。

  • 移除适用于 IEquatable<T> 的 Assert.AreEqual 重载:如果出现泛型类型推断错误,请显式将类型参数指定为 object。
3.8 移除 ExpectedExceptionAttribute

[ExpectedException] 属性已在 v4 中移除。在 MSTest 3.2 中,引入了 MSTEST0006 分析器,用于标记 [ExpectedException] 的用法,并建议在仍使用 v3 时迁移到 Assert.ThrowsExactly(这是非破坏性变更)。在 v4 中,该属性已完全移除。请迁移到 Assert.ThrowsExactly:

// Before (v3)
[ExpectedException(typeof(InvalidOperationException))]
[TestMethod]
public void TestMethod()
{
    MyCall();
}

// After (v4)
[TestMethod]
public void TestMethod()
{
    Assert.ThrowsExactly<InvalidOperationException>(() => MyCall());
}

如果抛出异常的调用前有测试初始化代码,请只将抛出异常的调用包装在 lambda 中——保持清晰的 Arrange/Act 分离:

// Before (v3)
[ExpectedException(typeof(ArgumentNullException))]
[TestMethod]
public void Validate_NullInput_Throws()
{
    var service = new ValidationService();
    service.Validate(null);  // throws here
}

// After (v4)
[TestMethod]
public void Validate_NullInput_Throws()
{
    var service = new ValidationService();
    Assert.ThrowsExactly<ArgumentNullException>(() => service.Validate(null));
}

对于异步测试方法,请使用 Assert.ThrowsExactlyAsync:

// Before (v3)
[ExpectedException(typeof(HttpRequestException))]
[TestMethod]
public async Task FetchData_BadUrl_Throws()
{
    await client.GetAsync("https://localhost:0");
}

// After (v4)
[TestMethod]
public async Task FetchData_BadUrl_Throws()
{
    await Assert.ThrowsExactlyAsync<HttpRequestException>(
        () => client.GetAsync("https://localhost:0"));
}

如果 [ExpectedException] 使用了 AllowDerivedTypes 属性,请改用 Assert.ThrowsAsync<T>(匹配基类类型),而不是 Assert.ThrowsExactlyAsync<T>(精确匹配类型)。

若要有针对性地迁移,请转换所提供源代码中所有带属性的方法,只包装预期会抛出异常的语句,将 arrange/初始化语句保留在 lambda 外,并运行受影响的测试。如果存在可编辑的项目文件,仅在说明文字中替换 API 并不完整。

3.9 移除的目标框架

MSTest v4 支持 .NET 8 及更高版本和 .NET Framework 4.6.2 及更高版本。平台专用的受支持目标还包括 uap10.0.16299(UWP);现代 UWP 和 WinUI 使用各自对应的、受支持的 Windows 专用 .NET TFM。已停止支持 .NET Core 3.1 至 .NET 7。

如果测试项目面向不受支持的框架,请更新 TargetFramework:


<TargetFramework>net6.0</TargetFramework>


<TargetFramework>net8.0</TargetFramework>
3.10 展开策略移至 TestMethodAttribute

UnfoldingStrategy 属性(在 MSTest 3.7 中引入)已从各个数据源属性(DataRowAttribute、DynamicDataAttribute)移至 TestMethodAttribute。

3.11 ConditionBaseAttribute.ShouldRun 重命名

ConditionBaseAttribute.ShouldRun 属性已重命名为 IsConditionMet。

3.12 内部化/移除的类型

之前公开的多个类型现在已改为内部类型或已移除:

  • MSTestDiscoverer、MSTestExecutor、AssemblyResolver、LogMessageListener
  • TestExecutionManager、TestMethodInfo、TestResultExtensions
  • UnitTestOutcomeExtensions、GenericParameterHelper
  • PlatformServices 程序集中的 ITestMethod(TestFramework 中的版本未更改)

如果你的代码引用了其中任何类型,请寻找替代方案或移除相关依赖。

步骤 4:处理行为变更

这些变更不会导致构建错误,但可能影响测试运行时行为。

症状原因修复方法
Azure DevOps 中的测试显示为新测试/测试历史丢失TestCase.Id 的生成方式已更改(4.3)无需修改代码;历史记录将重新建立基线
在 [ClassInitialize] 中访问 TestContext.TestName 时抛出异常v4 强制执行生命周期范围规则(4.2)将访问移至 [TestInitialize] 或测试方法中
未发现测试/测试发现失败TreatDiscoveryWarningsAsErrors 现在默认为 true(4.4)修复警告,或在 .runsettings 中将其设为 false
之前不会挂起的测试现在挂起AppDomain 默认已禁用(4.1)在 .runsettings 的 RunConfiguration 中将 DisableAppDomain 设为 false
v4 升级后,vstest.console 使用 MSTest.Sdk 时找不到测试MSTest.Sdk 默认使用 MTP;v4 在 MTP 模式下不再添加 Microsoft.NET.Test.Sdk(4.5)添加显式包并保留 MTP,设置 UseVSTest,或将 CI 切换为 dotnet test
分析器产生新的警告分析器严重级别已提升(4.6)修复警告,或在 .editorconfig 中抑制
4.1 DisableAppDomain 默认值为 true

AppDomain 默认处于禁用状态。在 .NET Framework 上,如果在 testhost 内运行(dotnet test 和 VS 的默认设置),MSTest 会自动重新启用 AppDomain。如果需要显式控制 AppDomain 隔离,请通过 .runsettings 设置:

<RunSettings>
  <RunConfiguration>
    <DisableAppDomain>false</DisableAppDomain>
  </RunConfiguration>
</RunSettings>
4.2 错误使用 TestContext 时会抛出异常

MSTest v4 在错误的生命周期阶段访问测试专属属性时会抛出异常:

  • TestContext.FullyQualifiedTestClassName -- 不能在 [AssemblyInitialize] 中访问
  • TestContext.TestName -- 不能在 [AssemblyInitialize] 或 [ClassInitialize] 中访问

修复方法:将 [ClassInitialize] 中访问 TestContext.TestName 的代码移至 [TestInitialize] 或单个测试方法中,以便使用每个测试各自的上下文。不要用 FullyQualifiedTestClassName 替代 TestName 作为变通方法——两者的语义不同。

4.3 TestCase.Id 生成方式已更改

TestCase.Id 的生成算法已更改,以修复长期存在的问题。这可能会影响 Azure DevOps 测试结果跟踪(例如,随时间跟踪测试失败情况)。无需修改代码,但请注意测试结果历史记录会出现中断。

4.4 TreatDiscoveryWarningsAsErrors 默认值为 true

v4 使用更严格的默认设置。现在,测试发现警告会被视为错误,这意味着以前即使存在测试发现问题仍能运行的测试,现在可能会彻底失败。如果升级后出现意外的测试失败(不是构建错误,而是未发现测试),请检查是否存在测试发现警告。调查期间如需恢复 v3 的行为:

<RunSettings>
  <MSTest>
    <TreatDiscoveryWarningsAsErrors>false</TreatDiscoveryWarningsAsErrors>
  </MSTest>
</RunSettings>

建议:修复根本的测试发现警告,而不是抑制此设置。

4.5 MSTest.Sdk 与 vstest.console 的兼容性

MSTest.Sdk 默认使用 Microsoft.Testing.Platform(MTP)模式。MSTest.Sdk v3 在该模式下仍会添加 Microsoft.NET.Test.Sdk;v4 则移除了这个不必要的引用。因此,CI 流水线如果单独调用 vstest.console,升级到 v4 后可能会立即发现 0 个测试。

选项 A —— 保留 MTP 并临时兼容 VSTest 测试发现:显式添加确切兼容的 Microsoft.NET.Test.Sdk 包。如果 MTP 仍是主要运行器,但暂时无法移除现有的 vstest.console 作业,这是影响最小的修复方法:

直接使用 PackageReference,并指定从已配置的软件源解析出的确切兼容版本。采用中央包管理时,在 Directory.Packages.props 中添加或更新 Microsoft.NET.Test.Sdk 的 PackageVersion,并让项目引用保持不带版本号。不要照搬固定的示例版本。

使用实际的 vstest.console 命令进行验证;dotnet test 的 MTP 运行通过,并不能证明 VSTest 能发现测试。

选项 B —— 将项目切换到 VSTest 模式:设置 UseVSTest 属性。此时 MSTest.Sdk 会添加 Microsoft.NET.Test.Sdk:

<PropertyGroup>
  <UseVSTest>true</UseVSTest>
</PropertyGroup>

保留第 2 步中解析出的确切 MSTest.Sdk v4 固定版本;此选项更改的是运行器,而不是选定的 MSTest 版本或目标框架。

选项 C —— 将 CI 切换到 dotnet test:将 CI 流水线中对 vstest.console 的调用替换为 dotnet test。此方法原生支持 MTP,也是 MSTest.Sdk 项目的长期推荐方案。

不要说此行为在 v4 之前就已存在:在 MTP 模式下移除传递引用 Microsoft.NET.Test.Sdk,是 v4 的一项破坏性行为变更。

4.6 分析器严重性级别变更

多个分析器的默认严重性级别已从 Info 提升为 Warning:

  • MSTEST0001、MSTEST0007、MSTEST0017、MSTEST0023、MSTEST0024、MSTEST0025
  • MSTEST0030、MSTEST0031、MSTEST0032、MSTEST0035、MSTEST0037、MSTEST0045

检查并修复所有新增警告;如果这些警告是有意保留的,则在 .editorconfig 中抑制它们。

步骤 5:验证
  1. 运行 dotnet build —— 确认没有错误,并检查所有新增警告
  2. 运行 dotnet test —— 确认所有测试均通过
  3. 将测试结果(通过/失败数量)与迁移前的基准进行比较
  4. 如果使用 Azure DevOps 测试跟踪,请注意 TestCase.Id 的变更可能会影响历史记录的连续性
  5. 检查是否有测试因测试发现规则变严格而被悄然遗漏

验证清单

  • 所有 MSTest 包均已更新到 4.x
  • 项目构建无错误
  • 所有测试均通过 dotnet test
  • 自定义 TestMethodAttribute 子类已针对 ExecuteAsync 和 CallerInfo 更新
  • ExpectedExceptionAttribute 已替换为 Assert.ThrowsExactly
  • Assert.ThrowsException 已替换为 Assert.ThrowsExactly(或 Assert.Throws)
  • 已移除 ClassCleanupBehavior 枚举用法
  • TestContext.Properties.Contains 已更新为 ContainsKey
  • 所有目标框架均为 net8.0+、net9.0、net462+、uap10.0.16299 或 WinUI
  • 已检查并处理行为变更
  • 迁移期间没有丢失测试(比较测试数量)

相关技能

  • writing-mstest-tests —— 了解现代 MSTest v4 断言 API 和测试编写最佳实践
  • run-tests —— 了解迁移后如何运行测试

常见问题

问题解决方案
自定义 TestMethodAttribute 仍重写 Execute改为返回 Task<TestResult[]> 的 ExecuteAsync
TestMethodAttribute("display name") 不再编译通过使用 TestMethodAttribute(DisplayName = "display name")
找不到 ClassCleanupBehavior 枚举移除枚举参数;现在 [ClassCleanup] 总是在类结束时运行。若要在程序集结束时清理,请使用 [AssemblyCleanup]
缺少 TestContext.Properties.Contains使用 ContainsKey——Properties 现在是 IDictionary<string, object>
找不到 ExpectedException 特性在测试主体中使用 Assert.ThrowsExactly<T>(() => ...) 替代
找不到 Assert.ThrowsException替换为 Assert.ThrowsExactly(对于派生类型则使用 Assert.Throws)
Assert.AreEqual 使用格式字符串参数时失败使用字符串插值:$"message {value}"
之前不会挂起的测试现在挂起AppDomain 默认已禁用;在 testhost 中运行 .NET Fx 时会自动重新启用
Azure DevOps 测试历史记录中断这是预期行为——TestCase.Id 的生成方式已更改;无需修改代码,结果会重新建立基准
发现警告现在会导致运行失败TreatDiscoveryWarningsAsErrors 默认值为 true;请修复发现警告
Net6.0/net7.0 目标无法编译更新为 net8.0——MSTest v4 支持 net8.0、net9.0、net462、uap10.0.16299、现代 UWP 和 WinUI

评论

支持 Markdown