继续this问题......我正在尝试对以下场景进行单元测试:
我有一个类允许一个方法调用一个方法来执行一些操作,如果它失败则等待一秒钟并重新调用该方法。
说我想调用一个方法DoSomething()...但是如果DoSomething()抛出异常,我希望能够重试最多调用它3次但等待1秒之间每次尝试。在这种情况下,单元测试的目的是验证当我们调用DoSomething()3次,每次重试之间等待1秒钟时,所花费的总时间> = 3秒。
不幸的是,我能想到测试它的唯一方法就是使用秒表计时......它有两个副作用......
如果有一种方法可以模拟出这种依赖性,那么我的测试能够更快地运行并且在结果中相当一致,那将是多么好的。不幸的是,我想不出办法这样做......所以我想我会问,看看你们中间是否有人遇到过这个问题......
答案 0 :(得分:8)
您可以创建一个Waiter
类,该类提供等待指定时间的方法Wait(int)
。对此方法进行测试,然后在单元测试中传入一个模拟版本,该版本只是跟踪要求等待的时间,但会立即返回。
例如(C#):
interface IWaiter
{
void Wait(int milliseconds);
}
class Waiter : IWaiter
{
public void Wait(int milliseconds)
{
// not accurate, but for the sake of simplicity:
Thread.Sleep(milliseconds);
}
}
class MockedWaiter : IWaiter
{
public void Wait(int milliseconds)
{
WaitedTime += milliseconds;
}
public int WaitedTime { get; private set; }
}
答案 1 :(得分:4)
诀窍是创建时间提供者的模拟。
在Ruby中,您只需使用模拟库:
now = Time.now Time.expects(:现在).returns(现在的)。一旦 Time.expects(:now).returns(现在+ 1).once 等
在Java中,它有点复杂。用您自己的“时钟”替换常规时间
interface Clock {
Date getNow();
}
然后,重写:
public String elapsedTime(Date d) {
final Date now = new Date();
final long ms = now.getTime() - d.getTime();
return "" + ms / 1000 + " seconds ago";
}
为:
public String elapsedTime(Date d, Clock clock) {
final Date now = clock.getNow();
final long ms = now.getTime() - d.getTime();
return "" + ms / 1000 + " seconds ago";
}
显然,时钟可以通过其他方式注入,但现在我们有一个可测试且可扩展的方法。
答案 2 :(得分:2)
我经常测试这样的重试尝试。我使用的方法是使超时可配置 - 然后我可以在我的测试中将其设置为零。在你的情况下你可以模拟对象DoSomething()被调用,期望3次调用它并将超时设置为0秒 - 然后你可以验证DoSomething()被立即调用3次。
另一种方法是使用一个接口ITimer,在每次调用DoSomething()之间调用Wait(int秒) - 然后你可以模拟出ITimer并验证Wait(int)被调用3次正确的秒数参数。然后,ITimer的具体实现会执行Thread.sleep或用于等待的任何方法。
答案 3 :(得分:2)
我写了一系列关于在C ++中测试计时器类的博客文章。正如其他人所说的那样,关键是将时间提供者分开然后嘲笑它。一旦你完成了这一切,其余的都很容易。如果您有兴趣,请参见此处:http://www.lenholgate.com/blog/2004/05/practical-testing.html。
答案 4 :(得分:1)
你的直觉是正确的。诀窍是将问题的不同部分分开,而不是将它们捆绑到一个类中。您需要一个实现DoSomething的Action对象,该对象与时间无关;一个Retryer对象,它将管理调用Action对象的尝试;和一个等待的时钟。您可以直接对Action对象进行单元测试,使用存根的Clock和Action对象对Retryer进行单元测试,并使Clock变得如此简单以至于它可以正常工作。
最重要的是,不要在测试中进行真正的睡眠,它太脆弱而且很慢。