我有一个包含 TestClasses 和 TestMethods 的 MSTest 项目。由于系统先决条件不合适,一些测试被忽略。尽管如此,测试似乎仍在运行。
这表明,在不满足系统先决条件的情况下,忽略的测试会生成一个消息框来警告用户并建议采取的操作。从 Visual Studio UI 运行选择测试时会出现这些消息框。我尝试在“调试”中运行测试,并在 MessageBox 的位置放置断点,但即使 MessageBox 由于某种原因出现并运行,调试器也不会在那里停止。将堆栈跟踪添加到 MessageBox,我发现 Ignored TestMethod 是调用的来源。
从概念上讲,我是否遗漏了一些关于 MSTest 的内容,可以解释为什么忽略和未选择的测试开始运行?
橡皮鸭 PS:
我的理解是,目标是忽略基于缺少系统先决条件的某些测试。在类似的情况下,对我有用的是下面的代码示例中显示的方法。请随意从我的 GitHub 存储库运行此示例,看看它是否提供您正在寻找的行为。 克隆
就问题可能是什么而言,在没有看到更多代码的情况下,您的评论指出:
运行时发现“系统先决条件未满足”。
但是您还引用了
[Ignore]
属性并提到了 #if
:
我可以尝试对 TestMethod 属性进行注释或 #ifdeffing 来回避问题,而不是添加 Ignored 属性。
两种替代方案(使用
#if
或应用 Ignore
属性)都与系统先决条件的运行时发现不一致。像 [Ignore]
这样的属性是在编译时评估的,一旦代码运行就无法动态更改。条件编译 (#if
) 也是一个编译时功能。这本质上也是静态的,不允许在运行时进行更改。在有效使用 #if
方面,下图显示了从项目属性窗口中取消选中自定义条件编译符号的立即效果。
相反,为了适应在运行时发现 “未满足系统先决条件”的情况,可以在
[ClassInitialize]
块中检查条件,然后在 [TestInitialize]
块中进行过滤。将测试断言为 Inconclusive
将具有跳过测试的所需行为。
public TestContext? TestContext { get; set; }
[TestInitialize]
public void TestInitialize()
{
if(_skippedTestMethods.Select(_=>_.Name).Contains(TestContext?.TestName))
{
// Test won't run at all.
// In RUN mode, will show as a skipped test.
// In DEBUG mode:
// - WILL: Break on Thrown
// - UNLESS: AssertInconclusiveException is disabled in Exception Settings window.
Assert.Inconclusive($"Requirement not met for {TestContext?.TestName}");
}
}
假设我们使用名为
[RuntimeRequirement]
的自定义属性来装饰三个测试
[TestMethod, RuntimeRequirement(Server1)]
public void TestSomethingA()
{
}
[TestMethod, RuntimeRequirement(Server2)]
public void TestSomethingB()
{
}
[TestMethod]
public void TestSomethingC()
{
}
这里,自定义属性是在同一个文件中定义的:
class RuntimeRequirementAttribute : Attribute
{
public RuntimeRequirementAttribute(string requirement)
{
Requirement = requirement;
}
public string Requirement { get; }
}
在此示例中,测试了服务器的可达性(或系统功能或其他......)。将显示非阻塞
MessageBox
,列出将跳过的测试,但允许测试继续执行而无需操作员输入。
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int MessageBox(IntPtr hWnd, String text, String caption, long type);
private const string Server1 = "http://ivsoftware.net"; // Reachable
private const string Server2 = "http://unreachable.bad"; // Unreachable
private static MethodInfo[] _skippedTestMethods = new MethodInfo[0];
private static readonly HttpClient httpClient = new HttpClient();
private static Thread? messageBoxThread = null;
[ClassInitialize]
public static async Task ClassInit(TestContext context)
{
List<string> _reachableServers = new List<string>();
List<string> _unreachableServers = new List<string>();
foreach (var url in new[] { Server1, Server2 })
{
if (await localIsServerReachableAsync(url))
{
_reachableServers.Add(url);
}
else
{
_unreachableServers.Add(url);
}
}
if (_unreachableServers.Any())
{
// Use reflection to find test methods with a RuntimeRequirement attribute that
// don't meet the requirement, then display them in a non-blocking message box.
_skippedTestMethods =
typeof(TestConditionals)
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Where(_ =>
_.GetCustomAttribute<RuntimeRequirementAttribute>() is RuntimeRequirementAttribute attr &&
_unreachableServers.Contains(attr.Requirement))
.ToArray();
// List skipped tests in a popup, but don't actually halt the execution.
// We'll await the MB in the [ClassCleanup]
messageBoxThread = new Thread(() =>
MessageBox(
IntPtr.Zero,
string.Join(Environment.NewLine, _skippedTestMethods.Select(_ => _.Name)),
"Skipped Tests", 0));
messageBoxThread.SetApartmentState(ApartmentState.STA);
messageBoxThread.Start();
}
#region L o c a l M e t h o d s
async Task<bool> localIsServerReachableAsync(string url)
{
try
{
using (var request = new HttpRequestMessage(HttpMethod.Head, url))
using (var response = await httpClient.SendAsync(request))
{
return response.IsSuccessStatusCode;
}
}
catch (HttpRequestException)
{
return false;
}
}
#endregion L o c a l M e t h o d s
}