Тестирование проблем с NMock на WPF и Dispatcher - PullRequest
2 голосов
/ 07 января 2011

Вот один для навязчивых фанатов.У меня есть этот метод:

    public void RefreshMelts()
    {
        MeltsAvailable.Clear();

        ThreadPool.QueueUserWorkItem(delegate
        {
            Dispatcher.BeginInvoke((ThreadStart)delegate
            {
                eventAggregator.GetEvent<BusyEvent>().Publish(true);
                eventAggregator.GetEvent<StatusMessageEvent>().Publish(
                    new StatusMessage("Loading melts...", MessageSeverity.Low));
            });

            try
            {
                IList<MeltDto> meltDtos = meltingAppService.GetActiveMelts();

                Dispatcher.Invoke((ThreadStart)delegate
                {
                    foreach (MeltDto availableMelt in meltDtos)
                    {
                        MeltsAvailable.Add(availableMelt);
                    }
                    OnPropertyChanged("MeltsAvailable");

                    eventAggregator.GetEvent<BusyEvent>().Publish(false);
                    eventAggregator.GetEvent<StatusMessageEvent>().Publish(
                        new StatusMessage("Melts loaded", MessageSeverity.Low));
                });
            }
            catch (ApplicationException ex)
            {
                log.Error("An error occurred in MeltsViewModel when attempting to load melts", ex);

                Dispatcher.Invoke((ThreadStart)delegate
                {
                    MeltsAvailable.Clear();

                    eventAggregator.GetEvent<StatusMessageEvent>().Publish(
                        new StatusMessage("Melt data could not be loaded because an error occurred; " +
                            "see the application log for detail",
                            MessageSeverity.High));
                    eventAggregator.GetEvent<BusyEvent>().Publish(false);
                });
            }

        });

    }

Это определено в пользовательском элементе управления WPF.MeltsAvailable является наблюдаемой коллекцией MeltDtos.Этот код прекрасно работает при запуске в самом приложении.

Проблема в том, что я хотел бы создать модульное тестирование, используя NMock, для проверки результатов этого метода, в частности, после вызова MeltsAvailable.В собственности есть несколько предметов.Вот метод теста:

    [TestMethod]
    public void GetAvailableMeltsTest()
    {
        MeltDto mockMelt1 = new MeltDto();
        MeltDto mockMelt2 = new MeltDto();

        mockMelt1.MeltIdentifier = "TST0001";
        mockMelt2.MeltIdentifier = "TST0002";

        IList<MeltDto> availableMelts = new List<MeltDto>();
        availableMelts.Add(mockMelt1);
        availableMelts.Add(mockMelt2);

        Expect.Exactly(1).On(service).Method("GetActiveMelts").Will(Return.Value(availableMelts));


        MeltsViewModel vm = new MeltsViewModel(aggregator, logger, service, configManagerFactory); // All of these are mock objects

        vm.RefreshMelts();
        Thread.Sleep(millisecondDelayForEventPublish * 100);

        mockery.VerifyAllExpectationsHaveBeenMet();

        Assert.AreEqual(vm.MeltsAvailable.Count, 2);
        Assert.AreEqual(vm.MeltsAvailable[0].MeltIdentifier, "TST0001");
        Assert.AreEqual(vm.MeltsAvailable[1].MeltIdentifier, "TST0002");

    }

Тест последовательно завершается неудачей на первом Assert.AreEqual.vm.MeltsAvailable в этот момент пуст.

Если я удаляю все потоки и оставляю это так:

    public void RefreshMelts()
    {
        MeltsAvailable.Clear();
        IList<MeltDto> meltDtos = meltingAppService.GetActiveMelts();
        foreach (MeltDto availableMelt in meltDtos)
        {
            MeltsAvailable.Add(availableMelt);
        }
        OnPropertyChanged("MeltsAvailable");
    }

Тест проходит.

Итак, очевидно,есть что-то, что ему не нравится в потоках - но даже включив Debug-> Exceptions-> CLR Exceptions-> Thrown и отключив Just My Code, я не получаю никаких исключений в RefreshMelts.

Самое странное, что вызов Dispatcher.Invoke, когда я загружаю объекты MeltDto в коллекцию MeltsAvailable, кажется, никогда не вызывается.Я могу покрыть весь раздел точками останова, и они никогда не будут поражены.Увеличение резьбы. Время ожидания в моем тесте даже до десяти секунд ничего не меняет.

Почему?Почему этот раздел не выполняется, почему я не могу войти в него или взломать его, почему я не получаю исключения, почему он отлично работает при выполнении, но не в тесте?

Большое спасибо, Стив

1 Ответ

5 голосов
/ 11 марта 2011

Dispatcher - это цикл сообщений, связанный с потоком выполнения. Он обрабатывает элементы в своей очереди, когда основной поток простаивает. В модульном тесте этого никогда не происходит. Поток занят, а затем завершается после завершения теста.

Если вы используете Visual Studio для запуска своих тестов, вы можете включить подсветку покрытия кода, и вы увидите, что код внутри Dispatcher.Invoke () никогда не вызывается (он будет отображаться красным).

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

public static class DispatcherHelper 
{ 
    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; 
    } 
}

В конце вашего теста (перед утверждениями) вызовите DispatcherHelper.DoEvents (). Это заставит Диспетчер обрабатывать ожидающие события, такие как те, которые добавляют элементы в наблюдаемую коллекцию модели представления. Затем вы можете проверить свойства модели представления, чтобы убедиться, что они установлены правильно.

...