C# 最小起订量测试计时器

C# 最小起订量测试计时器,c#,unit-testing,moq,C#,Unit Testing,Moq,我正在建立一个计时器类,但我无法通过测试。我想测试当计时器上的时间过去时是否调用了一个方法 我有以下课程: public class TimeOutTimer { private readonly ISubscriber _subscriber; private Timer _timer; public TimeOutTimer(ISubscriber subscriber) { _subscriber = subscriber; }

我正在建立一个计时器类,但我无法通过测试。我想测试当计时器上的时间过去时是否调用了一个方法

我有以下课程:

public class TimeOutTimer
{
    private readonly ISubscriber _subscriber;
    private Timer _timer;

    public TimeOutTimer(ISubscriber subscriber)
    {
        _subscriber = subscriber;
    }

    public void Start()
    {
        _timer = new Timer(1000);
        _timer.Start();
        _timer.Elapsed += TimerOnElapsed;
    }

    private void TimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
    {
        _subscriber.TimeReached();   
    }
}
使用最小起重量进行试验:

[Test]
public void Start_WithValidParameters_TriggersTimeReached()
{
    var subscriberMock = new Mock<ISubscriber>();
    var timer = new TimeOutTimer(subscriberMock.Object);

    timer.Start();

    subscriberMock.Verify(subscriber => subscriber.TimeReached());
}
[测试]
具有有效参数的公共无效开始\u触发器估计值()
{
var subscriberMock=new Mock();
var timer=新超时器(subscriberMock.Object);
timer.Start();
subscriberMock.Verify(subscriber=>subscriber.timereach());
}
如果我取出计时器并直接调用_subscriber.timereach(),它就会工作


我做错什么了吗?

好吧,您正在处理一个异步操作。如果在处理程序中放置断点并调试代码,并且在验证行上等待拼写,那么当您跨过验证行时,您将看到处理程序确实执行了。您的问题是,您的测试在处理程序有机会触发之前完成。

在您的示例中,事件应该在1000毫秒后调用,而您需要立即进行验证。显然,此时无法调用该事件

最简单的方法是在测试中加入一个线程睡眠

[Test]
public void Start_WithValidParameters_TriggersTimeReached()
{
    var subscriberMock = new Mock<ISubscriber>();
    var timer = new TimeOutTimer(subscriberMock.Object);        

    timer.Start();
    Thread.Sleep(1000);

    subscriberMock.Verify(subscriber => subscriber.TimeReached());
}
[测试]
具有有效参数的公共无效开始\u触发器估计值()
{
var subscriberMock=new Mock();
var timer=新超时器(subscriberMock.Object);
timer.Start();
睡眠(1000);
subscriberMock.Verify(subscriber=>subscriber.timereach());
}
然而,这并不是为基于时间的类生成单元测试的正确方法

正确的方法是创建一个
ITimer
接口,然后由基于
Timer
的适配器实现

然后,
ITimer
将成为
TimeoutTimer
的依赖项,并在构造函数中或作为属性传递给它


在测试中,您可以模拟计时器,使其与测试同步,而无需等待(例如,通过控制类手动触发事件)。

您必须在测试中引发经过的事件


我怀疑是新定时器引起了麻烦。您可以尝试使其可注入,然后将其注入测试并引发事件

如果通过测试进行调试,_subscriber.timereach()是否会进入该状态?我猜不会,因为订户被模拟了。时间甚至没有被调用。我是否需要在测试中“等待”计时器激活?嗯……但在回答您的问题时,当我在没有经过事件的情况下直接调用_subscriber.timereach()时,它会按预期工作,并且验证确认调用了模拟接口方法。如果您发布了
TimeOutTimer
的完整实现,那么您还应该实现
IDisposable
。事实上,您可以用
新的System.Threading.Timer(state=>subscriber.timereach(),null,1000,0)替换调用
新的TimeOutTimer(订阅者)
并完全消除
超时时间。我知道您这样做是为了学习,但实际上“测试框架”并没有什么好处。您的测试基本上验证计时器是否引发其已用事件。就个人而言,我会把它当作阅读,而不是测试。值得测试的是您的事件处理程序是否执行了它应该执行的操作。您知道这可能会导致更多的麻烦。想象一下,如果等待10分钟,您就不能运行等待10分钟的单元测试。更好的方法是注入计时器.@AD.Net,这是我在第二段中建议的。我完全同意你的看法,那时我走对了方向。我加了睡眠,它确实通过了测试。“由基于计时器的适配器实现”有点令人困惑,我认为您在这里指的是适配器模式。我将尝试你的建议,以更优雅的方式解决这个问题。谢谢你的帮助@Dante我的意思是,你需要一个接口来模拟,但是Timer并没有实现它,所以你需要使用适配器模式来创建一个类,它实现了一个拥有你需要的接口的Timer。您显然走对了。@sklivz是的,我刚刚通读了适配器模式,在这个场景中使用它非常有意义。非常感谢;)