这是一个针对线程迷的人。我有这个方法:
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的ObservableCollection。在应用程序本身中运行时,此代码可以很好地工作。
问题是我想使用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-&gt; Exceptions-&gt; CLR Exceptions-&gt;抛出,并关闭Just My Code,我完全没有例外在RefreshMelts中。
最奇怪的是,似乎永远不会调用我将MeltDto对象加载到MeltsAvailable集合中的Dispatcher.Invoke调用。我可以用断点覆盖整个部分,它们永远不会被击中。提升Thread.Sepep我的测试时间甚至高达十秒也没有任何变化。
为什么呢?为什么这部分没有执行,为什么我不能介入它或闯入它,为什么我没有得到异常,为什么它在执行中工作正常而不是在测试中?
非常感谢, 史蒂夫
答案 0 :(得分:5)
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()。这将触发Dispatcher处理未完成的事件,例如将项添加到视图模型的可观察集合中的事件。然后,您可以检查视图模型的属性以验证它们是否已正确设置。