如何在NUnit测试中调用WPF Textbox的setter

6
我想在NUnit中编写系统测试,并使用ms的UI自动化调用UI。
由于某些原因,我的调用失败了 - 我在网上找到了一些提示,让我能够编写编译测试,但是我的断言失败了。
以下是一个可编译的最小示例。我的问题是示例中失败的测试。
应用程序XAML
<Application x:Class="InvokeTest.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:InvokeTest"
             Startup="Application_Startup"/>

应用程序 CS

using System.Windows;

namespace InvokeTest
{
    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            var view = new MainWindow();
            var viewmodel = new MainWindowViewModel();
            view.DataContext = viewmodel;
            view.Show();
        }
    }
}

Window XAML

<Window x:Class="InvokeTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:InvokeTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
<TextBox x:Name="MyTextBox" x:FieldModifier="public" Text="{Binding TextProperty, UpdateSourceTrigger=PropertyChanged}" />
</Window>

窗口控制系统

using NUnit.Framework;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Controls;

namespace InvokeTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class MainWindowViewModel
    {
        string textfield;
        public string TextProperty
        {
            get { DebugLog("getter"); return textfield; }
            set { textfield = value; DebugLog("setter"); }
        }

        private void DebugLog(string function)
        {
            Debug.WriteLine(ToString() + " " + nameof(TextProperty) + " " + function + " was called. value: '" + textfield ?? "<null>" + "'");
        }

        [TestFixture, Apartment(ApartmentState.STA)]
        public class WPFTest
        {
            MainWindow view;
            MainWindowViewModel viewmodel;

            [SetUp]
            public void SetUp()
            {
                view = new MainWindow();
                viewmodel = new MainWindowViewModel();
                view.DataContext = viewmodel;
            }

            [Test]
            public void SetTextBox_NoAutomation()
            {
                string expected = "I want to set this";
                view.MyTextBox.Text = expected;
                Assert.AreEqual(expected, viewmodel.TextProperty);
                /*
                Test Name:  SetTextBox_NoAutomation
                Test Outcome:   Failed
                Result Message: 
                Expected: "I want to set this"
                But was:  null
                */
            }

            [Test]
            public void SetTextBox_UIAutomation()
            {
                string expected = "I want to set this";
                SetValue(view.MyTextBox, expected);
                Assert.AreEqual(expected, viewmodel.TextProperty);
                /*
                Test Name:  SetTextBox_UIAutomation
                Test Outcome:   Failed
                Result Message: 
                Expected: "I want to set this"
                But was:  null
                */
            }
            private static void SetValue(TextBox textbox, string value)
            {
                TextBoxAutomationPeer peer = new TextBoxAutomationPeer(textbox);
                IValueProvider provider = peer.GetPattern(PatternInterface.Value) as IValueProvider;
                provider.SetValue(value);
            }
        }
    }
}

编辑 #1:@Nkosi 指出我的xaml中存在绑定失败的问题
编辑 #2:添加了一些模板以便进行手动测试,并添加了一个不使用UI自动化显示行为的用例。这只是一个附注,UI自动化才是本问题的核心。


2
希望你已经知道了,单元测试并不是为了测试用户界面而设计的。虽然可以通过一些技巧让它工作,但在这个过程中你会遇到很多问题。通常情况下,UI测试是作为编码UI测试和脚本集成测试的一部分来完成的。 - Bradley Uffner
@BradleyUffner 是的,我知道 :) 实际上这是如此普遍,以至于你无法在谷歌上搜索UI自动化而不碰到Coded UI测试。我认为Coded UI测试更优秀,但我想深入了解一下UI自动化。 - Johannes
2个回答

2
实际上,您可以调用 TextBox.Text 属性
view.MyTextBox.Text = expected;

在你的看法中,你也绑定了视图模型的Text属性,而在你的测试中,视图模型有一个MyTextBox属性。其中之一需要更新以匹配。
public class MainWindowViewModel
{
    public string Text { get; set; }
}

[TestFixture, Apartment(ApartmentState.STA)]
public class WPFTest
{
    [Test]
    public void SetTextBox()
    {
        //Arrange
        var expected = "I want to set this";

        var view = new MainWindow();
        var viewmodel = new MainWindowViewModel();
        view.DataContext = viewmodel;

        //Act
        view.MyTextBox.Text = expected;

        //Assert
        Assert.AreEqual(expected, viewmodel.Text);
    }
}

我提交了绑定失败(我想是这样的 :))。你的建议在我的机器上不起作用 - 你能运行它吗? - Johannes
除了断言之外?没有。 - Johannes
在调试过程中设置一些断点并逐步执行测试。在viewmodel属性设置上放置一个断点,看看当您设置文本框时是否会被触发。还要考虑使viewmodel继承自INotifyPropertyChanged - Nkosi
INotifyPropertyChanged 是另一个方向,当视图模型改变时。我编辑了我的最小示例以支持手动测试,这样您就可以看到 GUI 所需的操作。 - Johannes

0

我需要显示窗口。 我认为这是为了启动消息泵。

如果有人能够提供详细信息,我将把该答案设置为被接受的答案。

using NUnit.Framework;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Controls;

namespace InvokeTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class MainWindowViewModel
    {
        string textfield;
        public string TextProperty
        {
            get { DebugLog("getter"); return textfield; }
            set { textfield = value; DebugLog("setter"); }
        }

        private void DebugLog(string function)
        {
            Debug.WriteLine(ToString() + " " + nameof(TextProperty) + " " + function + " was called. value: '" + textfield ?? "<null>" + "'");
        }

        [TestFixture, Apartment(ApartmentState.STA)]
        public class WPFTest
        {
            MainWindow view;
            MainWindowViewModel viewmodel;

            [SetUp]
            public void SetUp()
            {
                view = new MainWindow();
                viewmodel = new MainWindowViewModel();
                view.DataContext = viewmodel;
                view.Show();
            }

            [TearDown]
            public void TearDown()
            {
                view.Close();
                view.DataContext = null;
                view = null;
                viewmodel = null;
            }

            [Test]
            public void SetTextBox_NoAutomation()
            {
                string expected = "I want to set this";
                view.MyTextBox.Text = expected;
                Assert.AreEqual(expected, viewmodel.TextProperty);
            }

            [Test]
            public void SetTextBox_UIAutomation()
            {
                string expected = "I want to set this";
                SetValue(view.MyTextBox, expected);
                Assert.AreEqual(expected, viewmodel.TextProperty);
            }

            private void SetValue(TextBox textbox, string value)
            {
                TextBoxAutomationPeer peer = new TextBoxAutomationPeer(textbox);
                IValueProvider provider = peer.GetPattern(PatternInterface.Value) as IValueProvider;
                provider.SetValue(value);
            }
        }
    }
}

1
我认为原始版本中绑定不是AttachToContext。如果DataContext已经设置,那么在第一次调用UpdateLayout方法时,绑定很可能会被解析出来。第二个版本中,view.Show将触发UpdateLayout事件。 - zzczzc004

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接