c# - 自动化测试期间的 WPF 组件资源

标签 c# wpf xaml testing

我已经到了想编写一个自动化测试来验证绑定(bind)到特定状态下的 View 模型的 WPF View 的内容的地步。

在概念上,它相当简单。创建一个 View 模型,设置它的状态,创建适当的 View ,将 View 添加到窗口,设置 View 的数据上下文,显示窗口,截取屏幕截图,与之前截取的屏幕截图进行比较。此类测试对于检测意外更改以及验证所有 View 实际上都可以无错误地创建非常有用。

但是,创建我的 View 实例被证明是有问题的。它需要一组未包含在 XAML 定义本身中的资源。这些资源在实际应用中包含在应用层的资源字典中,所以在实际应用中创建View的时候,这些资源已经可供它使用了。

当我在我的测试中创建这个 View 的实例时,它会抛出一个 XamlParseException 关于无法找到各种资源(可以理解)。

我不想简单地向 View 本身的 XAML 定义添加适当的资源字典,因为这会增加创建这些 View 对象之一所需的工作量(计算机工作量),以及增加每个所需的内存量实例。我的理解是,这是 ResourceDictionary 没有以这种方式共享的结果。

我试过了:

  • 在测试中创建 App.xaml 的实例(设置 Application.Current 属性)。
  • 将 Application.Current.Resources 属性设置为 App.xaml 实例的 Resources 属性。
  • 直接设置 Page 的 Resources 属性。
  • 直接设置 Window 的 Resources 属性(为测试添加页面的窗口)。

  • 本质上,我需要知道是否有办法设计一种情况,我可以为 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/

    相关文章:

    c# - 查找哪个事件被触发

    c# - 将控件属性(例如颜色)数据绑定(bind)到未显示的字段

    c# - 使用 Prism,如何将作用域 RegionManager 注入(inject)服务?

    c# - RelayCommand canexecute 永远不会被重新评估

    wpf - 如何使用 Trigger 检查 DataGridColumnHeader 的 ColumnIndex 是否是最后一个?

    c# - Windows 注册表中的 ProductName,是否针对不同语言进行了本地化?

    c# - 我应该使用列表吗?

    wpf - WPF 中的实际可见性

    php - Windows 8 应用程序 C# 与 PHP 和 Mysql

    c# - MVVM中层的作用