您要么需要一个单独的线程来操作视图模型,要么需要在调度程序线程中执行代码来执行此操作。我更喜欢后者,但两者都可以。前者需要您小心地使用调度程序将某些操作封送到 UI 线程;后者需要您小心谨慎。视图模型属性更改不需要这样做,因为 WPF 会自动为您执行此操作,但其他操作(例如直接调用 UI 对象方法) - 例如Window.Close()
— do.
以下是您可以使用调度程序线程执行所有测试代码的示例:
[TestMethod]
public void TestWpfApp()
{
Thread thread = new Thread(() =>
{
var application = new App();
Application.ResourceAssembly = System.Reflection.Assembly.GetExecutingAssembly();
application.InitializeComponent();
application.Dispatcher.InvokeAsync(() =>
{
_TestApplication(application);
}, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
application.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}
private static async void _TestApplication(Application application)
{
Window window = application.MainWindow;
ViewModel viewModel = (ViewModel)window.DataContext;
await Task.Delay(TimeSpan.FromSeconds(5));
viewModel.Text = "Hello World!";
await Task.Delay(TimeSpan.FromSeconds(5));
window.Close();
}
基本结构是设置一个适合运行 WPF UI 的线程(它必须是 STA 线程,并且您不应该弄乱单元测试的线程,因此需要为此目的创建一个新线程),并且然后在该线程中,通过以下方式执行通常的 WPF 设置和队列InvokeAsync()
向调度程序调用主测试方法,使其在 WPF 代码开始运行后开始执行。
当然,这个例子假设ViewModel
类与Text
属性和主窗口的DataContext
属性设置为此的实例ViewModel
。在我的示例程序中,我只是绑定了Text
财产给一个TextBlock.Text
财产。显然,您可以对视图模型做任何您想做的事情。
请注意,我必须明确设置Application.ResourceAssembly
。在我目前使用的 Visual Studio Community 2017 中,单元测试框架在以下上下文中运行文本:Assembly.GetEntryAssembly()
回报null
,这会破坏 WPF 的资源加载。明确设置它可以解决这个问题(我正在使用Assembly.GetExecutingAssembly()
,因为我将单元测试代码与示例 WPF 程序放在同一个程序集中……显然,如果将它们保留在不同的程序集中,则必须以其他方式找到正确的程序集)。
在我的测试中,使用System.Windows.Threading.DispatcherPriority.ApplicationIdle
在通话中Dispatch.InvokeAsync()
没有严格要求。我找到了MainWindow
and DataContext
属性初始化良好。但我更喜欢明确地等待ApplicationIdle
,只是为了确保这些已完全初始化,并且 WPF 程序本身已准备好开始接受您想要进行测试的任何输入。