你可能不喜欢这个,因为它可能涉及你认为“丑陋的黑客”,但我更喜欢使用真正的 EventAggregator 而不是嘲笑一切。虽然表面上是外部资源,但 EventAggregator 在内存中运行,因此不需要太多设置、清理,并且不像其他外部资源(如数据库、Web 服务等)那样成为瓶颈,因此我觉得适合在单元测试中使用。在此基础上,为了测试,我使用这种方法来克服 NUnit 中的 UI 线程问题,而对我的生产代码的更改或风险最小。
首先,我创建了一个扩展方法,如下所示:
public static class ThreadingExtensions
{
private static ThreadOption? _uiOverride;
public static ThreadOption UiOverride
{
set { _uiOverride = value; }
}
public static ThreadOption MakeSafe(this ThreadOption option)
{
if (option == ThreadOption.UIThread && _uiOverride != null)
return (ThreadOption) _uiOverride;
return option;
}
}
然后,在我的所有活动订阅中,我使用以下内容:
EventAggregator.GetEvent<MyEvent>().Subscribe
(
x => // do stuff,
ThreadOption.UiThread.MakeSafe()
);
在生产代码中,这只是无缝地工作。出于测试目的,我所要做的就是在我的设置中添加它,并在我的测试中添加一些同步代码:
[TestFixture]
public class ExampleTest
{
[SetUp]
public void SetUp()
{
ThreadingExtensions.UiOverride = ThreadOption.Background;
}
[Test]
public void EventTest()
{
// This doesn't actually test anything useful. For a real test
// use something like a view model which subscribes to the event
// and perform your assertion on it after the event is published.
string result = null;
object locker = new object();
EventAggregator aggregator = new EventAggregator();
// For this example, MyEvent inherits from CompositePresentationEvent<string>
MyEvent myEvent = aggregator.GetEvent<MyEvent>();
// Subscribe to the event in the test to cause the monitor to pulse,
// releasing the wait when the event actually is raised in the background
// thread.
aggregator.Subscribe
(
x =>
{
result = x;
lock(locker) { Monitor.Pulse(locker); }
},
ThreadOption.UIThread.MakeSafe()
);
// Publish the event for testing
myEvent.Publish("Testing");
// Cause the monitor to wait for a pulse, but time-out after
// 1000 millisconds.
lock(locker) { Monitor.Wait(locker, 1000); }
// Once pulsed (or timed-out) perform your assertions in the real world
// your assertions would be against the object your are testing is
// subscribed.
Assert.That(result, Is.EqualTo("Testing"));
}
}
为了使等待和脉冲更简洁,我还在 ThreadingExtensions 中添加了以下扩展方法:
public static void Wait(this object locker, int millisecondTimeout)
{
lock (locker)
{
Monitor.Wait(locker);
}
}
public static void Pulse(this object locker)
{
lock (locker)
{
Monitor.Pulse(locker);
}
}
然后我可以这样做:
// <snip>
aggregator.Subscribe(x => locker.Pulse(), ThreadOption.UIThread.MakeSafe());
myEvent.Publish("Testing");
locker.Wait(1000);
// </snip>
同样,如果你的感受意味着你想使用模拟,那就去吧。如果您宁愿使用真实的东西,这可行。