我已经到了想编写一个自动化测试来验证绑定(bind)到特定状态下的 View 模型的 WPF View 的内容的地步。
在概念上,它相当简单。创建一个 View 模型,设置它的状态,创建适当的 View ,将 View 添加到窗口,设置 View 的数据上下文,显示窗口,截取屏幕截图,与之前截取的屏幕截图进行比较。此类测试对于检测意外更改以及验证所有 View 实际上都可以无错误地创建非常有用。
但是,创建我的 View 实例被证明是有问题的。它需要一组未包含在 XAML 定义本身中的资源。这些资源在实际应用中包含在应用层的资源字典中,所以在实际应用中创建View的时候,这些资源已经可供它使用了。
当我在我的测试中创建这个 View 的实例时,它会抛出一个 XamlParseException 关于无法找到各种资源(可以理解)。
我不想简单地向 View 本身的 XAML 定义添加适当的资源字典,因为这会增加创建这些 View 对象之一所需的工作量(计算机工作量),以及增加每个所需的内存量实例。我的理解是,这是 ResourceDictionary 没有以这种方式共享的结果。
我试过了:
本质上,我需要知道是否有办法设计一种情况,我可以为 WPF 组件的独立实例配置一组应用程序资源,以便在自动化测试中使用。
您可以通过创建以下结构来重现该问题,除了 View_Test.cs 文件位于一个项目中,View_Test.cs 文件位于测试项目中。运行应用程序,它可以工作。运行测试,它失败了。
应用程序.xaml
<Application
x:Class="Blah.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
样式文件
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="SpecialBrush" Color="Black" />
</ResourceDictionary>
主窗口.xaml
<Window
x:Class="Blah.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Blah="clr-namespace:Blah"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Blah:View/>
</Grid>
</Window>
查看.xaml
<UserControl
x:Class="AutomatedTestUserControlApplicationResources.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="{StaticResource SpecialBrush}">
</Grid>
</UserControl>
View_Test.cs
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Blah;
using System.Windows;
namespace Blah.Test
{
[TestClass]
public class View_Test
{
[TestMethod]
public void Test()
{
var view = new View();
var window = new Window();
window.Content = view;
window.ShowDialog();
}
}
}
更新:
我很幸运为有问题的 View 创建了一个额外的构造函数,它采用 ResourceDictionary,作为一种向 View 注入(inject)一些上下文以进行初始化的方法。此构造函数重载仅用于测试,在实际应用程序中,资源上下文已经从应用程序资源中可用。
public View(ResourceDictionary resourceContext = null)
{
if (resourceContext != null) Resources.MergedDictionaries.Add(resourceContext);
InitializeComponent();
}
这解决了我上面发布的特定示例,它不依赖于初始化不相关的对象只是为了让 View 工作(这与良好的依赖注入(inject)实践背道而驰)。
然而,当我试图在我的实际项目中实现它时,它带来了一些额外的问题。我在应用程序级别的资源上下文实际上是 4 个不同资源字典的合并,后者依赖于较早的(因为它们引用了较早条目中指定的资源)。
应用资源.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Style/GlobalColour.xaml"/>
<ResourceDictionary Source="Style/GlobalBrush.xaml"/> <!-- Dependent on GlobalColour-->
<ResourceDictionary Source="Style/GlobalStyle.xaml"/>
<ResourceDictionary Source="Resources/GlobalContent.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
在我的测试项目中从此文件创建一个 ResourceDictionary,然后在构建过程中将该 ResourceDictionary 注入(inject)我的 View 会引发与未找到 StaticResource 相关的 XamlParseException(无法找到的资源位于 GlobalBrush 中,并且依赖于 GlobalColour 中的条目) )。
我会在进一步探索时更新。
更新:
我绝对没有运气手动创建和使用上面的 AppResources ResourceDictionary。我无法让 MergedDictionaries 中的词典之间的相互依赖发挥作用。我什至无法手动展平 ResourceDictionary 实例,因为当我尝试访问依赖于并行字典中资源的字典中的资源时,它抛出了 XamlParseException。
因此,通过构造函数将 ResourceDictionary 注入(inject) View 的想法不适用于我的解决方案(尽管如果应用程序资源是平面 ResourceDictioanry,它可以工作)。
在这次旅程的最后,我得出的结论是,在 xaml 不直接包含对资源的引用(无需实例化整个应用程序)的情况下,实例化 View 的唯一方法是在任何地方包含对适当 ResourceDictionary 的引用直接在 xaml 中使用资源。然后,您必须在运行时管理性能问题(因为您正在实例化数百个重复的 ResourceDictionaries),方法是使用 SharedResourceDictionary(互联网上有许多此概念的实现)。
最佳答案
这实际上并不是那么难,您只需要使用 Application.LoadComponent创建所有内容的实例,以便在正确的时间提供正确的资源。
关键是通过其 XAML 加载所有内容,而不是创建类的实例,因为类只包含一半的信息。
[TestClass]
public class View_Test
{
[TestMethod]
public void Test()
{
//set initial ResourceAssembly so we can load the App
Application.ResourceAssembly = Assembly.GetAssembly(typeof (App));
//load app
var app = (App) Application.LoadComponent(new Uri("App.xaml", UriKind.Relative));
//load window and assign to app
var mainWindow = (Window) Application.LoadComponent(new Uri("MainWindow.xaml", UriKind.Relative));
app.MainWindow = mainWindow;
//load view and assign to window content
var view = (UserControl) Application.LoadComponent(new Uri("View.xaml", UriKind.Relative));
mainWindow.Content = view;
//show the window
mainWindow.Show();
}
}
编辑:更简单的版本
我只是看了一些反汇编的代码,看看它是如何在内部完成的,这可以简化为不需要 XAML 引用。使事情顺利进行的最重要的部分是设置
Application.ResourceAssembly
并创建 App
并调用 InitializeComponent
在上面。该窗口不是特别必要的,您可以创建一个新窗口来保存 View 。[TestClass]
public class View_Test
{
[TestMethod]
public void Test()
{
Application.ResourceAssembly = Assembly.GetAssembly(typeof (App));
var app = new App();
app.InitializeComponent();
var mainWindow = new MainWindow();
app.MainWindow = mainWindow;
var view = new View();
mainWindow.Content = view;
mainWindow.Show();
}
}
关于c# - 自动化测试期间的 WPF 组件资源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20797116/