在.NET中对System.Threading.Timer进行单元测试

12

如何在.NET中对基于System.Threading.Timer的计时器进行单元测试?

System.Threading.Timer具有回调方法。

4个回答

11
你可以进行单元测试,而不必实际上创建 System.Threading.Timer 的直接依赖关系。相反,创建一个 ITimer 接口,并创建一个实现它的 System.Threading.Timer 包装器。
首先需要将回调函数转换为事件,以便它可以成为接口的一部分:
public delegate void TimerEventHandler(object sender, TimerEventArgs e);

public class TimerEventArgs : EventArgs
{
    public TimerEventArgs(object state)
    {
        this.State = state;
    }

    public object State { get; private set; }
}

然后创建一个接口:

public interface ITimer
{
    void Change(TimeSpan dueTime, TimeSpan period);
    event TimerEventHandler Tick;
}

还有一个包装器:

public class ThreadingTimer : ITimer, IDisposable
{
    private Timer timer;

    public ThreadingTimer(object state, TimeSpan dueTime, TimeSpan period)
    {
        timer = new Timer(TimerCallback, state, dueTime, period);
    }

    public void Change(TimeSpan dueTime, TimeSpan period)
    {
        timer.Change(dueTime, period);
    }

    public void Dispose()
    {
        timer.Dispose();
    }

    private void TimerCallback(object state)
    {
        EventHandler tick = Tick;
        if (tick != null)
            tick(this, new TimerEventArgs(state));
    }

    public event TimerEventHandler Tick;
}

显然,您需要添加构造函数和/或Change方法的任何重载以从Threading.Timer中使用。现在,您可以使用虚拟计时器对依赖于ITimer的任何内容进行单元测试:

public class FakeTimer : ITimer
{
    private object state;

    public FakeTimer(object state)
    {
        this.state = state;
    }

    public void Change(TimeSpan dueTime, TimeSpan period)
    {
        // Do nothing
    }

    public void RaiseTickEvent()
    {
        EventHandler tick = Tick;
        if (tick != null)
            tick(this, new TimerEventArgs(state));
    }

    public event TimerEventHandler Tick;
}

每当你想模拟一个时钟周期时,只需在模拟对象上调用RaiseTickEvent方法即可。

[TestMethod]
public void Component_should_respond_to_tick
{
    ITimer timer = new FakeTimer(someState);
    MyClass c = new MyClass(timer);
    timer.RaiseTickEvent();
    Assert.AreEqual(true, c.TickOccurred);
}

这看起来不错,但是为什么你要创建自己的委托而不是直接使用BCL中的TimerCallback委托呢? - Shaun
1
@Shaun:因为那个委托不是有效的事件处理程序。事件处理程序有一个特定的签名:object sender,TEventArgs e,其中 TEventArgs:EventArgs - Aaronaught
请注意,现在闭包得到广泛支持。如果我想要快速且简单的东西,我可能不会费心声明和连接所有事件,而是直接传递一个Action - Aaronaught
关于标准事件处理程序签名的观点很好,当我尝试使用Moq来引发具有非标准签名的事件时,这曾经让我受挫。 - Shaun

1

我会像测试其他类一样进行测试,但时间间隔较短,以避免单元测试运行太长时间。 另一种方法是仅测试您自己的代码,并使用模拟计时器(例如NMock),但这取决于您的代码设计。您能发布一些代码片段吗?


0

如果时间只是改变了回调方法,那么你只需要检查该方法的正确性,而不是系统时间如何工作。

除此之外,我建议使用MultimediaTimer - 它更加精确。


你好,我想要一个每小时触发一次的计时器。你会推荐 MultimediaTimer 给所有人吗? :) - Tim Robinson
如果需要返回UI线程,请结合.NET的Dispatcher使用。 - Danny Varod

0
希望无论这个功能是什么,它都能允许配置计时器间隔。你应该能够使用该接口来注入适合单元测试的短间隔。我确实对这个测试的价值有些疑问:你是在测试间隔(毫无意义),还是测试例程是否实际上每隔一段时间被调用?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接