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

Pra*_*dda 4 c# nunit unit-testing 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);
}
Run Code Online (Sandbox Code Playgroud)

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

_view.Expect(v => v.SetTrackbarValue(100));
Run Code Online (Sandbox Code Playgroud)

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

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

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

Mer*_*ham 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);
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

然后你可以模拟或存根,IContext这样你就不必担心线程,并且可以使用一个只执行委托的简单实现.

如果我编写单元测试来模拟这一点,我还会编写更高级别的"集成"测试,这些测试没有模拟出来,但是进行了较少的细化验证.

  • @Prashant:然后我肯定会删除"上下文"类,而不是嘲笑它.让它直接调用传递给它的委托.在单元测试中,在视图上设置期望值而不是在上下文类中设置期望值.将此测试与几个简单的基于UI的集成测试结合起来,以确保在替换真正的`SynchronizationContext`实现时不会出现任何奇怪的错误. (2认同)