如何对涉及SynchronizationContext的代码进行单元测试?

时间:2011-12-02 09:03:08

标签: c# unit-testing nunit rhino-mocks

我有以下代码,我正在尝试使用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);
}

在测试用例中,我将期望设置为

_view.Expect(v => v.SetTrackbarValue(100));

当我验证期望时,单元测试失败并且消息

Test(s) failed. Rhino.Mocks.Exceptions.ExpectationViolationException :
IIndexTrackerView.SetTrackbarValue(100); Expected #1, Actual #0.

我在这里找不到问题,如何解决?

2 个答案:

答案 0 :(得分:6)

我通常通过使用可以实例化和模拟的抽象类或接口封装全局状态来解决这样的问题。然后,我将抽象类或接口的实例注入到使用它的代码中,而不是直接访问全局状态。

这让我嘲笑全局行为,并使我的测试不依赖于或行使不相关的行为。

这是你可以做到的一种方式。

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,这样你就不必担心线程,并且可以使用一个只执行委托的简单实现。

如果我编写了单元测试来模拟这一点,我还会编写更高级别的“集成”测试,但不会模拟它,但会进行较少的细化验证。

答案 1 :(得分:1)

我有一段时间没有使用过Rhino Mocks所以我不记得确切的语法但是如果像Merlyn所说的那样重构不是一个选项,那么另一个解决方案就是使用ManualResetEvent来等待你的模拟行为上。

这样的事情:

[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!");

}

这样你可以等到另一个线程触发事件,此时你可以继续。确保有一个合理的超时,所以你不要永远等待!