WPF 单元测试






4.71/5 (14投票s)
如何对 WPF 应用程序进行单元测试
引言
没有单元测试的开发是什么?
对我来说,那就是一片混乱!你修复了一个 bug,最终却带来了另外 3 个……同样,如果你没有单元测试,重构就变成了一种风险,你最终会说:“如果它能用,就别碰它”……
在使用 WPF 开发软件时,当你创建第一个单元测试时,你会遇到一个障碍。你会遇到 InvalidOperationException
的诅咒。NUnit 测试运行器会给你显示红灯,并告诉你:InvalidOperationException
无法设置指定的 COM 单元状态。如果你尝试使用 TestDriven.Net
Visual Studio 插件运行单元测试,这种情况就不会发生,因为该工具运行在 STA 模式下,而你的构建服务器 99% 运行在 MTA 模式下……所以我们必须解决这个问题!
问题原因
问题的原因是 NUnit 运行在 MTA 模式下,而 WPF 必须运行在 STA 模式下。我在 这里找到了解决这个问题的方法。我(经过一些小的修改)在我的 AvalonUnitTesting
库中使用了这个类。基本上,要运行与 WPF 相关的单元测试,你可以使用一个名为 AvalonTestRunner
的类(在本文随附的 AvalonUnitTesting.dll 中),并调用 RunInSta
方法,传递一个 delegate
。它将自动为你切换当前线程的单元状态到 STA……严格来说,你不是切换线程的单元状态,而是启动一个新的 STA 线程并在该线程中运行 delegate
。使用 AvalonTestRunner
进行此类测试的一个例子如下:
[Test]
public void TestMainWindow()
{
AvalonTestRunner.RunInSTA(
delegate
{
MainWindow window = new MainWindow();
Assert.AreEqual(300, window.Height);
});
}
不仅仅是断言
WPF 是一个非常酷、灵活且强大的平台……我就是想说一句:)
总之,回到单元测试……使用 WPF,你还可以测试用户交互,例如按钮点击。我在这方面找到了一篇非常有趣的文章:这里。
基本上,你可以使用 AutomationPeers
来引发当用户与你的应用程序进行交互时通常会引发的事件。我的库中没有支持这个功能,但也许有一天(如果我妻子允许我)我会……:)
单元测试数据绑定
当你提到“WPF”时,首先想到的是数据绑定功能。我想数据绑定是 WPF 最强大的功能之一。然而,当出现“糟糕”的错误时,什么都不会发生,什么都无法正常工作。发生这种情况的原因是 WPF 会为我们捕获异常,并将错误记录在 Trace 中(静默但致命)。实际上,当发生绑定错误时,你可以在 Visual Studio 调试窗口中看到错误,这是默认的跟踪侦听器。
如果有一种方法可以单元测试数据绑定,那不是很棒吗?所以,我决定深入研究一下,看看能做什么。我找到了一个关于 WPF 跟踪的非常好的文章,地址是 这里。这篇文章阐明了正在发生什么,以及如何创建一个小型类来为我进行数据绑定单元测试。我当时想,为什么不创建一个跟踪侦听器,在 WPF 数据绑定异常被引发时就进行断言呢?基本上,这个解决方案可以让你的 XAML 数据绑定可测试!所以我创建了一个类,它监听这些数据绑定警告,并在出现问题时进行断言。基本上,我实现的类(AvalonTraceListener
)只是一个在 WPF 记录错误时进行断言的 TraceListener
。所以在 WriteLine
方法中,你覆盖了你的 Assert
。
public override void WriteLine(string message)
{
if (currentWindowBeingTested != null)
currentWindowBeingTested.Close();
Assert.Fail(message);
}
所以,测试一个窗口或控件进行数据绑定的代码看起来是这样的:
[Test]
public void TestDataBindingForControls()
{
AvalonTestRunner.RunInSTA(delegate
{
AvalonTestRunner.RunDataBindingTests(new MainWindow());
AvalonTestRunner.RunDataBindingTests(new UserControlTests());
});
}
测试窗口/控件的过程非常简单。你传递一个包含你的 XAML 的对象的实例,然后 AvalonTestRunner
的 RunDataBindingTests
方法会检查控件是否是窗口,如果不是,它会用一个窗口控件包装你传递的控件。当我们有一个窗口实例时,我们调用窗口的 Show
方法。这将导致所有数据绑定被初始化,如果存在任何错误,我们的 TraceListener
将确保捕获它们并使单元测试失败。
你的控件未使用 Application.Resources
或任何其他共享资源非常重要,因为这会导致测试失败。例如,如果你有一个与 Application.Resources
合并的 ResourceDictionary
,测试将失败,因为找不到该 Resource
。在这种情况下,我建议的做法是重载你的窗口/控件的构造函数,并确保你添加了仅用于单元测试所需的资源,例如:
public Window1() : this(false)
{}
public Window1(bool unitTesting)
{
if(unitTesting)
Resources.MergedDictionaries.Add(new BrushesResources());
InitializeComponent();
}
然后通过实例进行单元测试,在构造函数中传递 true
-> new Window1(true)
。
在调用 InitializeComponents
之前合并你所需的 Resource
非常重要,因为 InitializeComponents
会尝试加载你的 XAML。一旦你准备好所有这些,你就可以开始了。你现在也可以开始进行 WPF 数据绑定的单元测试了……
下载该库,享受 WPF 的乐趣。
历史
- 2007 年 12 月 22 日:初始发布