Правильный метод использования WPF Dispatcher в модульных тестах - PullRequest
7 голосов
/ 18 февраля 2012

Я пытаюсь следовать рекомендациям Использование диспетчера WPF в модульных тестах , чтобы запустить мой тест nUnit.

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

[Test]
public void Data_Should_Contain_Items()
{
    DispatcherFrame frame = new DispatcherFrame();
        PropertyChangedEventHandler waitForModelHandler = delegate(object sender, PropertyChangedEventArgs e)
        {
          if (e.PropertyName == "Data")
          {
            frame.Continue = false;
          }
        };
    _myViewModel.PropertyChanged += waitForModelHandler;
    Dispatcher.PushFrame(frame);

    Assert.IsTrue(_myViewModel.Data.Count > 0, "Data item counts do not match");
}

Однако, если я попытаюсь использовать предложение DispatcherUtil, оно не будет работать:

[Test]
public void Data_Should_Contain_Items()
{
    DispatcherUtil.DoEvents();
    Assert.IsTrue(_myViewModel.Data.Count > 0, "Data item counts do not match");
}

public static class DispatcherUtil
{
    [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public static void DoEvents()
    {
        DispatcherFrame frame = new DispatcherFrame();
        Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
            new DispatcherOperationCallback(ExitFrame), frame);
        Dispatcher.PushFrame(frame);
    }

    private static object ExitFrame(object frame)
    {
        ((DispatcherFrame)frame).Continue = false;
        return null;
    }
}

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

Правильно ли я использую DispatcherUtil? Похоже, лучше использовать метод для обработки диспетчера, а не ждать обратных вызовов от модели представления.

Ответы [ 2 ]

7 голосов
/ 19 февраля 2012

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

Dispatcher.CurrentDispatcher.BeginInvoke(..

Диспетчер является внешней зависимостью и не должен быть частью ваших модульных тестов - мы можем предположить, что диспетчер работает.

Я бы использовал инъекцию зависимостей (бедняков, Unity и т. Д.). Создайте подходящий интерфейс, представляющий диспетчера. Создайте реальную реализацию, которая обернет настоящего диспетчера. Создайте поддельную реализацию, которая использует Action.BeginInvoke. В подделке вы записываете все IAsyncResults, возвращенные вызовам BeginInvoke.
Затем создайте вспомогательный метод, который будет ожидать завершения всех вызовов, которые вы можете использовать в своем тесте для ожидания завершения.

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

2 голосов
/ 23 июня 2014

Я нашел простое решение.Вместо того, чтобы обрабатывать Frames Async в Dispatcher, я добавил синхронизированный метод в класс DispatcherUtil.Вызов этого метода DoEventsSync () возвращает, когда все кадры были обработаны, я думаю, что это должно помочь здесь:

    public static class DispatcherUtil
    {
        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        public static void DoEvents()
        {
            var frame = new DispatcherFrame();
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
                new DispatcherOperationCallback(ExitFrame), frame);
            Dispatcher.PushFrame(frame);
        }

        public static void DoEventsSync()
        {
            var frame = new DispatcherFrame();
            Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background,
                new DispatcherOperationCallback(ExitFrame), frame);
            Dispatcher.PushFrame(frame);
        }

        private static object ExitFrame(object frame)
        {
            ((DispatcherFrame)frame).Continue = false;
            return null;
        }
    }

Теперь просто используйте DispatcherUtil.DoEventsSync(); вместо DispatcherUtil.DoEvents(); в модульных тестах.Вы можете быть уверены, что Диспетчер обработал все до того, как продолжатся юнит-тесты.Для тестов не нужно добавлять обратные вызовы.

Смущает то, что DispatcherUtil.DoEvents(); действительно является DispatcherUtil.DoEventsAsync();, поскольку BeginInvoke(..) является асинхронным методом

...