从单元测试项目启动和调用 WPF 应用程序的方法
Launch and call methods on WPF app from unit test project
我想启动一个 WPF 应用程序并调用 ViewModel 上的方法来控制该应用程序以进行集成测试。类似于:
[Test]
public void Test1()
var application = new MyApp();
application.InitializeComponent();
application.Run();
(好的,这会在此时停止测试执行,大概是将控制权传递给 WPF 应用程序。不确定如何处理这个问题。在单独的线程中启动或其他什么?)
然后我希望能够在 ViewModel 上获取和设置值,如下所示:
application.MyViewModel.SomeProperty = "A value!";
这里的目标是在不求助于 WinAppDriver、White、CodedUI 或任何类似的混乱的情况下,以集成的方式测试 WPF 应用程序。想法?
您需要一个单独的线程来操作视图模型,或者您需要在调度程序线程中执行代码来执行此操作。我更喜欢后者,但两者都行。前者要求您谨慎使用调度程序将某些操作编组到 UI 线程;视图模型 属性 更改不需要那个,因为 WPF 会自动为您执行此操作,但其他事情如直接调用 UI 对象方法——例如Window.Close()
— 做。
下面是您可以使用调度程序线程执行所有测试代码的示例:
[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线程,不要乱用单元测试的线程,因此需要为此目的创建一个新线程),然后在该线程中,通过 InvokeAsync()
对调度程序进行通常的 WPF 设置和队列调用主要测试方法,使其在 WPF 启动后开始执行代码已经开始 运行.
当然,这个例子假设 ViewModel
class 和 Text
属性,以及主要 window 的 DataContext
属性 设置为此 ViewModel
的一个实例。在我的示例程序中,我只是将 Text
属性 绑定到 TextBlock.Text
属性。显然,您可以对视图模型做任何您想做的事情。
请注意,我必须明确设置 Application.ResourceAssembly
。在我目前正在使用的 Visual Studio Community 2017 中,单元测试框架在 Assembly.GetEntryAssembly()
returns null
的上下文中运行文本,这打破了 WPF 的资源加载。设置它明确修复了这个问题(我正在使用 Assembly.GetExecutingAssembly()
,因为我将单元测试代码与我的示例 WPF 程序放在同一个程序集中......显然,如果你将它们放在不同的程序集中,你必须找到以其他方式正确组装)。
在我的测试中,在对 Dispatch.InvokeAsync()
的调用中使用 System.Windows.Threading.DispatcherPriority.ApplicationIdle
并不是严格要求的。我发现 MainWindow
和 DataContext
属性初始化良好。但我更喜欢显式等待 ApplicationIdle
,只是为了确保它们已完全初始化,并且 WPF 程序本身已准备好开始接受您为测试准备的任何输入。
在单元测试或集成测试中创建 App
并对其调用 Run()
没有多大意义。每个 AppDomain
.
只能创建一个 App
您应该做的是以某种方式构建您的代码,这样您就不必单独测试 ViewModel class 之外的任何其他内容。这意味着您的所有应用程序逻辑都应该在 ViewModel 中实现,或者在 ViewModel 使用一种或另一种方式的 classes 中实现。
这是一种称为 Model-View-ViewModel (MVVM) 的设计模式,它是 开发时推荐使用的设计模式 XAML-based UI应用程序。这是有原因的——测试就是其中之一。
如果你搜索一下,你会发现很多关于 MVVM 的信息。 This 应该是一个很好的起点。
我想启动一个 WPF 应用程序并调用 ViewModel 上的方法来控制该应用程序以进行集成测试。类似于:
[Test]
public void Test1()
var application = new MyApp();
application.InitializeComponent();
application.Run();
(好的,这会在此时停止测试执行,大概是将控制权传递给 WPF 应用程序。不确定如何处理这个问题。在单独的线程中启动或其他什么?)
然后我希望能够在 ViewModel 上获取和设置值,如下所示:
application.MyViewModel.SomeProperty = "A value!";
这里的目标是在不求助于 WinAppDriver、White、CodedUI 或任何类似的混乱的情况下,以集成的方式测试 WPF 应用程序。想法?
您需要一个单独的线程来操作视图模型,或者您需要在调度程序线程中执行代码来执行此操作。我更喜欢后者,但两者都行。前者要求您谨慎使用调度程序将某些操作编组到 UI 线程;视图模型 属性 更改不需要那个,因为 WPF 会自动为您执行此操作,但其他事情如直接调用 UI 对象方法——例如Window.Close()
— 做。
下面是您可以使用调度程序线程执行所有测试代码的示例:
[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线程,不要乱用单元测试的线程,因此需要为此目的创建一个新线程),然后在该线程中,通过 InvokeAsync()
对调度程序进行通常的 WPF 设置和队列调用主要测试方法,使其在 WPF 启动后开始执行代码已经开始 运行.
当然,这个例子假设 ViewModel
class 和 Text
属性,以及主要 window 的 DataContext
属性 设置为此 ViewModel
的一个实例。在我的示例程序中,我只是将 Text
属性 绑定到 TextBlock.Text
属性。显然,您可以对视图模型做任何您想做的事情。
请注意,我必须明确设置 Application.ResourceAssembly
。在我目前正在使用的 Visual Studio Community 2017 中,单元测试框架在 Assembly.GetEntryAssembly()
returns null
的上下文中运行文本,这打破了 WPF 的资源加载。设置它明确修复了这个问题(我正在使用 Assembly.GetExecutingAssembly()
,因为我将单元测试代码与我的示例 WPF 程序放在同一个程序集中......显然,如果你将它们放在不同的程序集中,你必须找到以其他方式正确组装)。
在我的测试中,在对 Dispatch.InvokeAsync()
的调用中使用 System.Windows.Threading.DispatcherPriority.ApplicationIdle
并不是严格要求的。我发现 MainWindow
和 DataContext
属性初始化良好。但我更喜欢显式等待 ApplicationIdle
,只是为了确保它们已完全初始化,并且 WPF 程序本身已准备好开始接受您为测试准备的任何输入。
在单元测试或集成测试中创建 App
并对其调用 Run()
没有多大意义。每个 AppDomain
.
App
您应该做的是以某种方式构建您的代码,这样您就不必单独测试 ViewModel class 之外的任何其他内容。这意味着您的所有应用程序逻辑都应该在 ViewModel 中实现,或者在 ViewModel 使用一种或另一种方式的 classes 中实现。
这是一种称为 Model-View-ViewModel (MVVM) 的设计模式,它是 开发时推荐使用的设计模式 XAML-based UI应用程序。这是有原因的——测试就是其中之一。
如果你搜索一下,你会发现很多关于 MVVM 的信息。 This 应该是一个很好的起点。