如何测试棱镜事件聚合订阅,在UI线程?
-
24-09-2019 - |
题
我有一个类,即订阅经由棱镜事件聚合器的事件。
由于这是有点难以嘲笑事件聚合器如所指出的此处一>,我只实例化一个真实的和被测它传递给该系统。
在我的测试中,我然后通过这个聚合发布事件,然后检查如何在我的系统下测试做出反应吧。由于该事件将由FileSystemWatcher的生产过程中引发的,我想通过订阅的UIThread,所以一旦事件引发我可以更新我的UI尽量使用自动调度的。
但问题是,在测试过程中,事件永远不会在系统中发现被测试,除非我不上UIThread订阅。
我使用MSpec用于我的测试,这是我从内部VS2008经由TDD.Net运行。
添加[RequiresSta]
我的测试类没有帮助
没有人有一种解决方案,即节省了我从在我的测试改变ThreadOption(例如,经由一个属性 - 什么的丑陋的黑客)???
解决方案
如果你嘲笑这两个事件和事件聚集,并使用最小起订量的回调,你可以做到这一点。
下面是一个例子:
Mock<IEventAggregator> mockEventAggregator;
Mock<MyEvent> mockEvent;
mockEventAggregator.Setup(e => e.GetEvent<MyEvent>()).Returns(mockEvent.Object);
// Get a copy of the callback so we can "Publish" the data
Action<MyEventArgs> callback = null;
mockEvent.Setup(
p =>
p.Subscribe(
It.IsAny<Action<MyEventArgs>>(),
It.IsAny<ThreadOption>(),
It.IsAny<bool>(),
It.IsAny<Predicate<MyEventArgs>>()))
.Callback<Action<MyEventArgs>, ThreadOption, bool, Predicate<MyEventArgs>>(
(e, t, b, a) => callback = e);
// Do what you need to do to get it to subscribe
// Callback should now contain the callback to your event handler
// Which will allow you to invoke the callback on the test's thread
// instead of the UI thread
callback.Invoke(new MyEventArgs(someObject));
// Assert
其他提示
我真的觉得你应该用嘲笑的一切,而不是EventAggregator。这是不难的嘲笑......我并不认为链接的答案证明了很多关于EventAggregator的可测试性东西。
这是你的测试。我不使用MSpec,但这里的起订量中测试。您没有提供任何代码,所以我基于此的链接代码。你的情况是不是合作方案,因为其他OP只是想知道如何验证订阅是被称为有点困难,但实际上你要调用在传递的方法订阅...些更困难的,但不是非常
//Arrange!
Mock<IEventAggregator> eventAggregatorMock = new Mock<IEventAggregator>();
Mock<PlantTreeNodeSelectedEvent> eventBeingListenedTo = new Mock<PlantTreeNodeSelectedEvent>();
Action<int> theActionPassed = null;
//When the Subscribe method is called, we are taking the passed in value
//And saving it to the local variable theActionPassed so we can call it.
eventBeingListenedTo.Setup(theEvent => theEvent.Subscribe(It.IsAny<Action<int>>()))
.Callback<Action<int>>(action => theActionPassed = action);
eventAggregatorMock.Setup(e => e.GetEvent<PlantTreeNodeSelectedEvent>())
.Returns(eventBeingListenedTo.Object);
//Initialize the controller to be tested.
PlantTreeController controllerToTest = new PlantTreeController(eventAggregatorMock.Object);
//Act!
theActionPassed(3);
//Assert!
Assert.IsTrue(controllerToTest.MyValue == 3);
您可能不喜欢这个,因为它可能涉及你的感觉是一个“丑陋的黑客攻击”,但我的选择是使用一个真正的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>
此外,如果你的感情的意思是你想用嘲笑,去了。如果你想用真实的东西,这样的作品。