自动测试期间的WPF组件资源

时间:2013-12-27 08:03:24

标签: c# wpf xaml testing

我已经达到了这样的程度,我想编写一个自动化测试来验证WPF视图的内容,该视图在特定状态下与视图模型绑定。

在概念上,它相当简单。创建视图模型,设置其状态,创建适当的视图,将视图添加到窗口,设置视图的数据上下文,显示窗口,截取屏幕截图,与先前截取的屏幕截图进行比较。这种测试对于检测意外更改非常有用,并且可以验证是否可以无错误地创建所有View。

然而,创建我的View实例证明是有问题的。它需要一组未包含在XAML定义中的资源。这些资源包含在实际应用程序中的应用程序级资源字典中,因此在实际应用程序中创建View时,这些资源已经可用。

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

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

我试过了:

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

基本上,我需要知道是否有办法设计一种情况,我可以为自动测试中使用的WPF组件的独立实例配置一组应用程序资源。

您可以通过创建以下结构来重现该问题,除了一个项目中的View_Test.cs文件之外的所有文件,以及生活在测试项目中的View_Test.cs文件。运行应用程序,它的工作原理。运行测试但失败。

的App.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>

Styles.xaml

<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>

MainWindow.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>

View.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,作为一种为其初始化注入一些上下文的方法。此构造函数重载仅用于测试,在实际应用程序中,资源上下文已从Application资源中可用。

public View(ResourceDictionary resourceContext = null)
{
    if (resourceContext != null) Resources.MergedDictionaries.Add(resourceContext);
    InitializeComponent();
}

这解决了我上面发布的一个特定示例,它不依赖于初始化不相关的对象,只是为了让View工作(面对良好的依赖注入实践)。

然而,当我尝试在我的实际项目中实现它时,它揭示了一些其他问题。我在应用程序级别的资源上下文实际上是4个不同资源字典的合并,后者依赖于前面的(因为它们引用了前面条目中指定的资源)。

AppResources.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注入到我的View中会抛出与未找到的StaticResource相关的XamlParseException(无法找到的资源存在于GlobalBrush中,并且依赖于进入GlobalColour)。

我会在进一步探索时更新。

更新

我绝对没有运气手动创建和使用上面的AppResources ResourceDictionary。我无法使MergedDictionaries中的字典之间的相互依赖性起作用。我甚至无法手动压平ResourceDictionary实例,因为当我尝试访问依赖于并行字典中的资源的字典中的资源时,它会抛出XamlParseException。

因此,通过构造函数将ResourceDictionary注入View的想法在我的解决方案中使用是不可行的(尽管如果应用程序资源是一个平坦的ResourceDictioanry它可以工作)。

在这个旅程结束时,我得出的结论是,实例化一个视图,其中xaml不直接包含对资源的引用(不必实例化整个应用程序)的唯一方法是包含对相应的引用ResourceDictionary无论在何处使用资源,都直接在xaml中。然后,您必须在运行时管理性能问题(因为您要使用SharedResourceDictionary实例化数百个重复的ResourceDictionaries)(互联网上有许多此概念的实现)。

3 个答案:

答案 0 :(得分:11)

实际上,您只需要使用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。窗口没有必要,你可以创建一个新窗口来保存视图。

[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();
    }
}

答案 1 :(得分:2)

您在此处的内容是UI测试。微软的Coded UI框架将是您最好的选择。截取您的应用程序的屏幕截图并不是确定它正常工作的可靠方法。

使用Coded UI测试,您可以针对UI记录操作并添加断言以验证UI的行为是否正常。当您执行测试时,它实际启动应用程序并回放您记录的操作以使应用程序进入预期状态。开始时通常很轻松。

当然,UI测试最好谨慎使用,因为它们要慢得多。合理的单元测试应该花费几毫秒才能运行,但几乎任何UI测试都需要花费几个数量级的时间。

答案 2 :(得分:0)

系统不关心它找到所需资源的位置。他们需要在视觉树的某个地方找到它们。这就是为什么可以将样式添加到窗口而不是应用程序。

考虑到这一点,我只需使用XAMLReader加载style.xaml,从而生成ResourceDictionary对象。然后将此资源分配/添加到Window对象。