【问题标题】:Is it possible to Mock out time in a unit test?是否可以在单元测试中模拟时间?
【发布时间】:2010-11-21 12:40:20
【问题描述】:

this 问题之后...我正在尝试对以下场景进行单元测试:

我有一个类,它允许调用方法来执行某些操作,如果失败,请稍等片刻并调用该方法。

假设我想调用一个方法 DoSomething()...但是如果 DoSomething() 抛出异常,我希望能够重试调用它最多 3 次,但之间等待 1 秒每次尝试。在这种情况下,单元测试的目的是验证当我们调用 DoSomething() 3 次并在每次重试之间等待 1 秒时,所花费的总时间 >= 3 秒。

不幸的是,我想测试它的唯一方法是使用秒表计时......它有两个副作用......

  1. 执行测试需要 3 秒...我通常希望我的测试在毫秒内运行
  2. 运行测试的时间会相差 +/- 10 毫秒左右,这可能会导致测试失败,除非我考虑到这种差异。

如果有一种方法可以模拟这种对时间的依赖性,这样我的测试能够更快地运行并且结果相当一致,那就太好了。不幸的是,我想不出办法……所以我想我会问一下,看看你们中是否有人遇到过这个问题……

【问题讨论】:

    标签: unit-testing time mocking


    【解决方案1】:

    您可以创建一个 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; }
    }
    

    【讨论】:

      【解决方案2】:

      诀窍是创建时间提供程序的模拟。

      在 Ruby 中,您只需使用模拟库:

      现在 = Time.now Time.expects(:now).returns(now).once Time.expects(:now).returns(now + 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";
      }
      

      显然时钟可以通过其他方式注入,但现在我们有了一个可测试和可扩展的方法。

      【讨论】:

      • 在 C# 3.0 中,您可以将 Clock 接口替换为 Func
      • 有用于 Java 的模拟库。以 EasyMock 为例。
      • 对于 Java,代码通常从 new Date() 中获取当前时间。使用 EasyMock 没有帮助,因为您无法覆盖返回的日期。因此引入了包装“时钟”类。抱歉,我没有解释清楚。
      【解决方案3】:

      我经常测试这样的重试尝试。我使用的方法是使超时可配置 - 然后我可以在我的测试中将其设置为零。在您的情况下,您可以模拟调用 DoSomething() 的对象,期望对其进行 3 次调用并将超时设置为 0 秒 - 然后您可以验证 DoSomething() 被立即调用了 3 次。

      另一种方法是使用 ITimer 接口,在每次调用 DoSomething() 之间调用 Wait(int seconds) - 然后你可以模拟 ITimer 并验证 Wait(int) 被调用 3 次秒数的正确参数。 ITimer 的具体实现然后执行 Thread.sleep 或您用于等待的任何方法。

      【讨论】:

        【解决方案4】:

        我写了一系列关于在 C++ 中测试计时器类的博客文章。正如其他人所说,关键是分离出时间提供者,然后将其模拟出来。一旦你完成了,剩下的就很容易了。如果您有兴趣,请参阅此处:http://www.lenholgate.com/blog/2004/05/practical-testing.html

        【讨论】:

          【解决方案5】:

          你的直觉是正确的。诀窍是将问题的不同部分分开,而不是将它们捆绑到一个类中。您需要一个实现独立于时间的 DoSomething 的 Action 对象;一个 Retryer 对象,它将管理调用 Action 对象的尝试;和一个等待的时钟。您可以直接对 Action 对象进行单元测试,使用存根的 Clock 和 Action 对象对 Retryer 进行单元测试,并使 Clock 变得如此简单以至于可以正常工作。

          最重要的是,不要在测试中加入真正的睡眠,它太脆弱而且太慢了。

          【讨论】:

            猜你喜欢
            • 2017-03-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-11-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多