单元测试 system.timers.timer。

16

我一直在阅读有关使用定时器和线程进行单元测试的问题。我找到了关于单元测试system.threading.timers的SO问题,但是我需要对system.timers.timer进行单元测试,而包装类似乎无法像这个一样顺利地工作。

我只需要知道如何模拟定时器和/或系统时间以进行单元测试。我似乎在谷歌上找不到相关信息。

编辑和更新: 如果我将下面的定时器进行封装,那么提取出定时器后,我可以生成一个定时器并使用模拟替换它的不同定时器。然后,重要的部分是获取在运行时注入的定时器(原始的,而不是模拟)并测试其经过时间后的事件代码。


1
由于您没有提供任何关于要覆盖哪个测试用例以及哪个代码块代表测试用例下的逻辑的信息,因此很难提出建议。 - sll
3
您需要测试计时器还是测试每个事件中执行的代码?如果是这样,您可以将该逻辑放在另一个类中进行测试。您可以信任计时器调用它。 - Ivo
@ivowiblo:我不知道怎么将你的评论标记为答案,但我认为那是我找到的最接近实际答案的回答。 - deltree
2个回答

27

你为什么不把这个东西包起来呢?

public interface ITimer
{
    void Start(double interval);
    void Stop();
    event ElapsedEventHandler Elapsed;
}

这基本上是你的接口所需要的全部内容。让我们看看它的运作方式(注意,你当然可以暴露更多的 Timer 属性,但这基本上是足够的基础知识):

public class MyTimer : ITimer
{
    private Timer timer = new Timer();

    public void Start(double interval)
    {
        timer.Interval = interval; 
        timer.Start();
    }

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

    public event ElapsedEventHandler Elapsed
    {
        add { this.timer.Elapsed += value; }
        remove { this.timer.Elapsed -= value; }
    }
}

现在,如果我们选择使用FakeItEasy作为我们的mocking框架,那么我们将如何在测试中利用它:

var timerFake = A.Fake<ITimer>();
var classUnderTest = new MyClass(timerFake);

// tell fake object to raise event now
timerFake.Elapsed += Raise.With<ElapsedEventArgs>(ElapsedEventArgs.Empty).Now;

// assert whatever was supposed to happen as event response, indeed did
Assert.That(classUnderTest.ReceivedEvent, Is.True);

上面的例子实际上测试的是定时器事件被触发后发生的代码。考虑以下MyClass的代码:

public class MyClass
{
    private ITimer timer;

    public MyClass(ITimer timer)
    {
        this.timer = timer;
        this.timer.Elapsed += TimerElapsedHandler;
    }

    public bool ReceivedEvent { get; set; }

    private void TimerElapsedHandler(object sender, ElapsedEventArgs e)
    { 
        ReceivedEvent = true;
    }
}
在测试中,我们会在需要时强制计时器启动,并检查TimerElapsedHandler代码是否执行,通过断言ReceivedEvent属性是否被设置。实际上,这个方法可能做了更多事情,但这只会改变我们进行断言的方式——想法仍然是一样的。

编辑:你也可以尝试 Moles,这是一个允许你生成任何框架类型/方法的虚假的框架。然而,如果模拟计时器是你想要的所有功能,我建议使用包装器方法。


如果我这样包装它,如何控制经过的事件和/或模拟对象? - deltree
@deltree:你所说的“如何控制事件”是什么意思?如果您这样包装它,您可以像任何其他接口一样模拟它。我会添加一个简单的例子。 - k.m
谢谢您。这更接近我实际说的话,而且显然不是我想要表达的意思。这是一种很好的单元测试方式,可以测试定时器是否被调用,但不能测试它到期后运行的代码。 - deltree
@deltree:这实际上确实测试了计时器到期时运行的代码-请参见我的编辑。 - k.m
timerFake.Elapsed += Raise.With<ElapsedEventArgs>(ElapsedEventArgs.Empty).Now; 会导致编译器错误,指出“参数类型'System.EventArgs'无法分配给参数类型'System.Timers.ElapsedEventArgs'”。是否有替代方法? - user1713336

2

您需要测试计时器还是测试每个事件执行的代码?如果是这样,您可以将该逻辑放在另一个类中并进行测试。您可以相信计时器会调用它...


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