Модульные тесты WPF Bindings

Я пытаюсь выполнить модульное тестирование привязок данных WPF с помощью тестового набора, предоставленного Microsoft Team System. Я хотел бы иметь возможность тестировать привязки без отображения окна, потому что большинство моих тестов будут для пользовательских элементов управления, а не для окна. Возможно ли это или есть способ лучше? Приведенный ниже код работает, если я показываю окно, но если я этого не делаю, привязки не обновляются.

            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

person NotDan    schedule 01.12.2008    source источник


Ответы (5)


Шейн, если вас действительно беспокоит незаметное прерывание привязки, вам следует подумать о перенаправлении следов привязки туда, где вы можете ее изучить. Я бы начал здесь:

http://blogs.msdn.com/mikehillberg/archive/2006/09/14/WpfTraceSources.aspx

В остальном я согласен с Гишу в том, что привязки не являются хорошими кандидатами для модульного тестирования, в основном из-за происходящей автомагии, которую Гишу упомянул в «Эпилоге». Вместо этого сосредоточьтесь на том, чтобы убедиться, что базовый класс ведет себя правильно.

Также обратите внимание, что вы можете получить еще более надежные трассировки, используя класс PresentationTraceSources:

http://msdn.microsoft.com/en-us/library/system.diagnostics.presentationtracesources.aspx

Надеюсь, это поможет!

person Bob King    schedule 03.12.2008

В поисках решения для преобразования ошибок привязки WPF в исключение я понял, что его также можно использовать в проекте модульного тестирования.

Техника очень проста:

  1. Получите TraceListener, который выдает вместо регистрации
  2. Добавьте этого слушателя в PresentationTraceSources.DataBindingSource

См. полное решение на GitHub, оно включает проект модульного тестирования.

Неудачный тест в Visual Studio

person Benoit Blanchon    schedule 26.10.2013

Взгляните на это.
Такая декларативная разметка редко ломается ... если только кто-то не обратится к руководству и не испортит. Даже в этом случае вы можете исправить это за считанные минуты. ИМХО стоимость написания таких тестов намного перевешивает преимущества.

Обновление [3 декабря, 08]: Хорошо.
Тест просто проверяет, имеет ли текстовое поле значение "FirstName" в качестве свойства Path привязки. Если я изменю / реорганизую FirstName на JustName в фактическом объекте источника данных, тест все равно пройдет, поскольку он проверяет анонимный тип. (Зеленый тест при нарушении кода - TDD Antipattern: The Liar) Если ваша цель - убедиться, что FirstName указано в XAML,

Assert.AreEqual("FirstName", txtBoxToProbe.GetBindingExpression(TextBox.TextProperty).ParentBinding.Path.Path);

Если вы действительно должны отловить сломанные привязки с помощью модульных тестов (и не хотите показывать пользовательский интерфейс), используйте реальный источник данных ... какое-то время боролся и придумал это.

[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 было непросто.

person Gishu    schedule 01.12.2008
comment
Если мы привязываемся к свойствам в классе и реорганизуем класс, xaml все равно будет компилироваться, но исключение не будет создано, и наше приложение больше не будет работать правильно, поскольку привязки будут неправильными. Это уже проблема для нас, поэтому мы ищем решение. - person NotDan; 02.12.2008

Объединив советы, с которыми я столкнулся в ряде сообщений SO, я написал следующий класс, который очень хорошо работает для тестирования привязок 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);
        }
    }
}
person chillitom    schedule 13.07.2012

вы можете попробовать Guia. С его помощью вы можете провести модульное тестирование своего UserControl и проверить правильность привязки данных. Но вы должны показать окно.

Вот пример. Он запускает новый экземпляр вашего UserControl и устанавливает его DataContext, а затем проверяет, установлено ли в текстовом поле правильное значение.

    [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();
    }
person treze    schedule 04.11.2010