如何对涉及SynchronizationContext的代码进行单元测试
本文关键字:代码 单元测试 SynchronizationContext | 更新日期: 2023-09-27 18:16:20
我有以下代码,我试图使用NUnit和Rhino模拟进行测试。
void _tracker_IndexChanged(object sender, IndexTrackerChangedEventArgs e)
{
// _context is initialised as
// SynchronizationContext _context = SynchronizationContext.Current;
// _tracker.Index is stubbed to return the value 100
_context.Post(o => _view.SetTrackbarValue(_tracker.Index), null);
}
在testcase中,我将期望设置为
_view.Expect(v => v.SetTrackbarValue(100));
,当我验证期望时,单元测试随机失败并显示消息
Test(s) failed. Rhino.Mocks.Exceptions.ExpectationViolationException :
IIndexTrackerView.SetTrackbarValue(100); Expected #1, Actual #0.
我在这里没有发现问题,我该如何解决它?
我通常通过使用可以实例化和模拟的抽象类或接口封装全局状态来解决此类问题。然后,我将抽象类或接口的实例注入到使用它的代码中,而不是直接访问全局状态。
这使我可以模拟全局行为,从而使我的测试不依赖或执行不相关的行为。
有一种方法可以做到。
public interface IContext
{
void Post(SendOrPostCallback d, Object state);
}
public class SynchronizationContextAdapter : IContext
{
private SynchronizationContext _context;
public SynchronizationContextAdapter(SynchronizationContext context)
{
_context = context;
}
public virtual void Post(SendOrPostCallback d, Object state)
{
_context.Post(d, state);
}
}
public class SomeClass
{
public SomeClass(IContext context)
{
_context = context;
}
void _tracker_IndexChanged(object sender, IndexTrackerChangedEventArgs e)
{
_context.Post(o => _view.SetTrackbarValue(_tracker.Index), null);
}
// ...
}
然后你可以模拟或存根IContext
,这样你就不必担心线程,可以使用一个简单的实现,只执行委托。
如果我编写单元测试来模拟它,我也会编写更高级别的"集成"测试,它不会模拟它,但具有更少的粒度验证。
我在一段时间内没有使用Rhino mock,所以我不记得确切的语法,但如果重构的东西作为Merlyn建议不是一个选项,那么另一个解决方案是使用ManualResetEvent等待,直到你的mock已采取行动。
像这样:
[Test]
public void ATest(){
ManualResetEvent completed = new ManualResetEvent(false);
_view.Expect(v => v.SetTrackbarValue(100)).Do(() => completed.Set());
//Stuff done here...
Assert.IsTrue(completed.WaitOne(1000), "Waited a second for a call that never arrived!");
}
这样,您可以等待,直到其他线程触发该事件,此时您可以继续。确保有一个合理的超时,这样你就不会永远等待!