【问题标题】:Unit testing code which gets current time获取当前时间的单元测试代码
【发布时间】:2010-10-17 09:00:22
【问题描述】:

为获取当前时间的代码编写单元测试的最佳方法是什么?例如,某些对象可能仅在工作日创建,其他对象在检查执行某些操作的权限时会考虑当前时间等。

我想我应该模拟 Date.today 和 Time.now。这是正确的方法吗?

更新:(a) Time.is 和 (b) Time.stubs(:now).returns(t) 两种解决方案都有效。 (a) 是非常好的方法,但 (b) 解决方案将与其他测试代码更加一致。

question,作者要求提供通用解决方案。对于 Ruby,在我的选项中,上述两个解决方案是 simpler,因此比提取获取当前日期/时间的代码更好。

顺便说一句,我建议使用Chronic 来获得所需的时间,例如

require 'Chronic'    
mon = Chronic.parse("next week monday")
Time.stubs(:now).returns(mon)

【问题讨论】:

  • 为什么不能是社区wiki?
  • 它可以,但是对于大多数特定的编程问题,它并没有完成,因此人们可以获得声誉。我没有答案,所以它不会影响我,但我很好奇。
  • 不是真正的重复,因为这个问题是特定于 Ruby 的。但是上面的链接可能会有所帮助。

标签: ruby-on-rails ruby unit-testing datetime


【解决方案1】:

模拟 Time.now 或 Date.today 看起来很简单,看起来像:

require 'rubygems'
require 'test/unit'
require 'mocha'

class MyClass

  def foo
    Time.now
  end

end

class MyTest < Test::Unit::TestCase

  def test_foo
    assert true
    t = Time.now
    Time.expects(:now).returns(t)
    assert_equal t, MyClass.new.foo
  end

end

【讨论】:

    【解决方案2】:

    以下来自Jay Field's Thoughts。它允许您在块的持续时间内重新定义Time.now

    require 'time'
    
    class Time
      def self.metaclass
        class << self; self; end
      end
    
      def self.is(point_in_time)
        new_time = case point_in_time
          when String then Time.parse(point_in_time)
          when Time then point_in_time
          else raise ArgumentError.new("argument should be a string or time instance")
        end
        class << self
          alias old_now now
        end
        metaclass.class_eval do
          define_method :now do
            new_time
          end
        end
        yield
        class << self
          alias now old_now
          undef old_now
        end
      end
    end
    
    Time.is(Time.now) do
      Time.now # => Tue Nov 13 19:31:46 -0500 2007
      sleep 2
      Time.now # => Tue Nov 13 19:31:46 -0500 2007
    end
    
    Time.is("10/05/2006") do
      Time.now # => Thu Oct 05 00:00:00 -0400 2006
      sleep 2
      Time.now # => Thu Oct 05 00:00:00 -0400 2006
    end
    

    【讨论】:

      【解决方案3】:

      将 ITimeProvider 对象创建为依赖项比传递时间要好,因为它符合 Don't Repeat Yourself 原则。

      在您的生产代码中的某处,必须获取当前时间。您可以将日期生成代码置于测试覆盖范围之外,或者您可以拥有一个易于测试的对象,该对象可以在其他任何地方。

      【讨论】:

      • 通常,获取当前时间是一个表达式,例如Java 中的新日期()。将其抽象为接口是荒谬的过度工程。你最终可能会比以前更多地重复自己。
      • 当然不是每个项目都适用。如果我严重依赖时间,我只会考虑做这样的事情。
      • 你是说因为生产代码和测试代码都产生时间而违反了 DRY?如果是这样,用模拟替换它有什么帮助?此外,“或者你可以拥有一个可以在其他任何地方的易于测试的对象”中是否缺少一个词?
      【解决方案4】:

      就在我的脑海中,我想最好的方法是不要让你的对象本身获得时间。换句话说,将日期/时间传递给当前正在使用内置时间构造的对象上调用的任何方法。根据您的情况,这可能是一个比模拟 Date.today 和 Time.now 更简单的解决方案,如您所建议的。

      编辑:我说这与将 ITimeProvider 接口作为依赖项传入的建议形成鲜明对比……在我看来,这只是矫枉过正。

      【讨论】:

        【解决方案5】:

        将 ITimeProvider(例如)类传递给您的例程以用于获取时间,然后您可以模拟它并使用模拟对象始终为您提供一致的时间供例程使用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-12-15
          • 1970-01-01
          • 2020-07-30
          • 1970-01-01
          • 2010-10-24
          • 1970-01-01
          • 1970-01-01
          • 2015-02-04
          相关资源
          最近更新 更多