单元测试WPF绑定

单元测试WPF绑定,wpf,unit-testing,data-binding,binding,Wpf,Unit Testing,Data Binding,Binding,我正在尝试使用Microsoft Team System提供的测试套件对我的WPF数据绑定进行单元测试。我希望能够在不显示窗口的情况下测试绑定,因为我的大多数测试都是针对用户控件的,而不是在窗口上。这是可能的还是有更好的方法?如果我显示窗口,下面的代码可以工作,但是如果我不显示,绑定不会更新 Window1_Accessor target = new Window1_Accessor(); UnitTestingWPF.Window1_Access

我正在尝试使用Microsoft Team System提供的测试套件对我的WPF数据绑定进行单元测试。我希望能够在不显示窗口的情况下测试绑定,因为我的大多数测试都是针对用户控件的,而不是在窗口上。这是可能的还是有更好的方法?如果我显示窗口,下面的代码可以工作,但是如果我不显示,绑定不会更新

            Window1_Accessor target = new Window1_Accessor();
            UnitTestingWPF.Window1_Accessor.Person p = new UnitTestingWPF.Window1_Accessor.Person() { FirstName = "Shane" };
            Window1 window = (target.Target as Window1);
            window.DataContext = p;         
            //window.Show(); //Only Works when I actually show the window
            //Is it possible to manually update the binding here, maybe?  Is there a better way?
            Assert.AreEqual("Shane", target.textBoxFirstName.Text);  //Fails if I don't Show() the window because the bindings aren't updated

盯着它看。
这种声明性标记很少中断。。除非有人把手册搞砸了。即使这样,你也可以在几分钟内修复它。在我看来,编写此类测试的成本远远超过其好处

更新[2008年12月3日]:那好吧。
测试只是测试textbox是否将值“FirstName”作为绑定的路径属性。如果我在实际数据源对象中将FirstName更改/重构为JustName,测试仍然会通过,因为它是针对匿名类型进行测试的。(代码中断时的绿色测试-TDD反模式:说谎者) 如果您的目的是验证是否在XAML中指定了FirstName

Assert.AreEqual("FirstName", txtBoxToProbe.GetBindingExpression(TextBox.TextProperty).ParentBinding.Path.Path);
如果您确实必须通过单元测试捕获损坏的绑定(并且不想显示UI),请使用真实的数据源。。。挣扎了一会儿,想出了这个

[Test]
public void TestTextBoxBinding()
{
   MyWindow w = new MyWindow();
   TextBox txtBoxToProbe = w.TextBox1;
   Object obDataSource = w;               // use 'real' data source 

   BindingExpression bindingExpr = BindingOperations.GetBindingExpression(txtBoxToProbe, TextBox.TextProperty);
   Binding newBind = new Binding(bindingExpr.ParentBinding.Path.Path);
   newBind.Source = obDataSource;
   txtBoxToProbe.SetBinding(TextBox.TextProperty, newBind);

   Assert.AreEqual("Go ahead. Change my value.", txtBoxToProbe.Text);
} 
结语: 调用
Window.Show()
时发生了一些问题。它以某种方式神奇地设置了DataItem属性,然后数据绑定开始工作

// before show
bindingExpr.DataItem => null
bindingExpr.Status => BindingStatus.Unattached

// after show
bindingExpr.DataItem => {Actual Data Source}
bindingExpr.Status => BindingStatus.Active
一旦绑定激活,我想你可以通过这样的代码强制文本框更新

txtBoxToProbe.GetBindingExpression(TextBox.TextProperty).UpdateTarget();

我再次表示,我不愿意反对这种做法。让NUnit在STA中运行是一件痛苦的事情。

Shane,如果您真正担心的是绑定会悄无声息地中断,那么您应该考虑将绑定跟踪重定向到您可以检查的地方。我从这里开始:

除此之外,我同意Gishu的观点,即绑定不是单元测试的好候选,主要是因为Gishu在“结语”中提到的automagic正在进行。相反,重点是确保底层类的行为正确

还请注意,使用PresentationTraceSources类可以获得更健壮的跟踪:

希望有帮助

你可以试试。 使用它,您可以单元测试您的UserControl并检查数据绑定是否正确。不过你得把窗户打开

这里有一个例子。它启动UserControl的新实例并设置其DataContext,然后检查textbox是否设置为正确的值

    [TestMethod]
    public void SimpleTest()
    {
        var viewModel = new SimpleControlViewModel() {TextBoxText = "Some Text"};

        customControl = CustomControl.Start<SimpleUserControl>((control) => control.DataContext = viewModel);

        Assert.AreEqual("Some Text", customControl.Get<TextBox>("textbox1").Value);

        customControl.Stop();
    }
[TestMethod]
public-void-SimpleTest()
{
var viewModel=new simpletrolviewmodel(){TextBoxText=“Some Text”};
customControl=customControl.Start((control)=>control.DataContext=viewModel);
Assert.AreEqual(“某些文本”,customControl.Get(“textbox1”).Value);
customControl.Stop();
}

结合我在许多文章中遇到的建议,我编写了以下类,该类非常适合测试WPF绑定

public static class WpfBindingTester
{
    /// <summary>load a view in a hidden window and monitor it for binding errors</summary>
    /// <param name="view">a data-bound view to load and monitor for binding errors</param>
    public static void AssertBindings(object view)
    {
        using (InternalTraceListener listener = new InternalTraceListener())
        {
            ManualResetEventSlim mre = new ManualResetEventSlim(false);

            Window window = new Window
            {
                Width = 0,
                Height = 0,
                WindowStyle = WindowStyle.None,
                ShowInTaskbar = false,
                ShowActivated = false,
                Content = view
            };

            window.Loaded += (_, __) => mre.Set();
            window.Show();

            mre.Wait();

            window.Close();

            Assert.That(listener.ErrorMessages, Is.Empty, listener.ErrorMessages);
        }
    }

    /// <summary>Is the test running in an interactive session. Use with Assume.That(WpfBindingTester.IsAvailable) to make sure tests only run where they're able to</summary>
    public static bool IsAvailable { get { return Environment.UserInteractive && Process.GetCurrentProcess().SessionId != 0; } }


    private class InternalTraceListener : TraceListener
    {
        private readonly StringBuilder _errors = new StringBuilder();
        private readonly SourceLevels _originalLevel;
        public string ErrorMessages { get { return _errors.ToString(); } }

        static InternalTraceListener() { PresentationTraceSources.Refresh(); }

        public InternalTraceListener()
        {
            _originalLevel = PresentationTraceSources.DataBindingSource.Switch.Level;
            PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Error;
            PresentationTraceSources.DataBindingSource.Listeners.Add(this);
        }

        public override void Write(string message) {}

        public override void WriteLine(string message) { _errors.AppendLine(message); }

        protected override void Dispose(bool disposing)
        {
            PresentationTraceSources.DataBindingSource.Listeners.Remove(this);
            PresentationTraceSources.DataBindingSource.Switch.Level = _originalLevel;
            base.Dispose(disposing);
        }
    }
}
公共静态类WpfBindingTester
{
///在隐藏窗口中加载视图并监视其绑定错误
///用于加载和监视绑定错误的数据绑定视图
公共静态void资产绑定(对象视图)
{
使用(InternalTraceListener侦听器=新的InternalTraceListener())
{
ManualResetEventSlim mre=新的ManualResetEventSlim(错误);
窗口=新窗口
{
宽度=0,
高度=0,
WindowStyle=WindowStyle.None,
ShowInTaskbar=false,
ShowActivated=false,
内容=视图
};
window.Loaded+=(u,u)=>mre.Set();
window.Show();
mre.Wait();
window.Close();
Assert.That(listener.ErrorMessages,Is.Empty,listener.ErrorMessages);
}
}
///测试是否在交互式会话中运行。请与Aspect.That(WpfBindingTester.IsAvailable)一起使用,以确保测试仅在能够运行的位置运行
公共静态bool可用{get{return Environment.UserInteractive&&Process.GetCurrentProcess().SessionId!=0;}}
私有类InternalTraceListener:TraceListener
{
私有只读StringBuilder_errors=new StringBuilder();
私有只读源级别_originalLevel;
公共字符串错误消息{get{return _errors.ToString();}}
静态InternalTraceListener(){PresentationTraceSources.Refresh();}
公共内部TraceListener()
{
_originalLevel=PresentationOnTraceSources.DataBindingSource.Switch.Level;
PresentationOnTraceSources.DataBindingSource.Switch.Level=SourceLevel.Error;
PresentationOnTraceSources.DataBindingSource.Listeners.Add(此);
}
公共重写无效写入(字符串消息){}
公共重写无效写线(字符串消息){{u errors.AppendLine(消息);}
受保护的覆盖无效处置(布尔处置)
{
PresentationOnTraceSources.DataBindingSource.Listeners.Remove(此);
PresentationOnTraceSources.DataBindingSource.Switch.Level=\u originAlleLevel;
基地。处置(处置);
}
}
}

在寻找将WPF绑定错误转换为异常的解决方案时,我发现它也可以用于单元测试项目

技术非常简单:

  • 派生一个抛出而不是记录的
    TraceListener
  • 将该侦听器添加到
    PresentationOnTraceSources.DataBindingSource
  • 请参阅,它包括一个单元测试项目


    如果我们绑定到类中的属性并重构该类,xaml仍将编译,但不会引发异常,我们的应用程序将不再正常运行,因为绑定将不正确。这对我们来说已经是一个问题,这就是为什么我们正在寻找解决方案。