如何伪造Time.now?


71

Time.now为在单元测试中测试时间敏感方法的目的而设置的最佳方法是什么?


6
有TimeLord红宝石吗?:P
罗布

3
关!有Timecop。(请参阅下面的答案。)
James A. Rosen

Answers:


76

我真的很喜欢Timecop库。您可以按块形式进行时间扭曲(就像时间扭曲一样):

Timecop.travel(6.days.ago) do
  @model = TimeSensitiveMode.new
end
assert @model.times_up!

(是的,您可以嵌套块形式的时间旅行。)

您还可以进行声明性的时间旅行:

class MyTest < Test::Unit::TestCase
  def setup
    Timecop.travel(...)
  end
  def teardown
    Timecop.return
  end
end

在这里有一些Timecop的黄瓜帮手。他们让您执行以下操作:

Given it is currently January 24, 2008
And I go to the new post page
And I fill in "title" with "An old post"
And I fill in "body" with "..."
And I press "Submit"
And we jump in our Delorean and return to the present
When I go to the home page
I should not see "An old post"

最后一次测试的末尾应该有引号吗?
安德鲁·格林

43

我个人更喜欢使时钟可注射,如下所示:

def hello(clock=Time)
  puts "the time is now: #{clock.now}"
end

要么:

class MyClass
  attr_writer :clock

  def initialize
    @clock = Time
  end

  def hello
    puts "the time is now: #{@clock.now}"
  end
end

但是,许多人更喜欢使用模拟/存根库。在RSpec / flexmock中,您可以使用:

Time.stub!(:now).and_return(Time.mktime(1970,1,1))

或在摩卡咖啡中:

Time.stubs(:now).returns(Time.mktime(1970,1,1))

1
测试部分正是我要提供的答案。
nitecoder

这完全也是我要做的。如果您以易于测试的方式编写代码,则不必为了使它可测试而做一些不可思议的事情。
8月Lilleaas,2009

5
虽然我同意以一种更具可测试性的方式编写东西的一般想法,但有时它根本不切实际。而Time.now是这些示例之一。它可以在系统的许多部分中使用,并且一直传递它会带来太多开销。
维塔利·库什纳

14

我正在使用RSpec,我这样做是:在调用Time.now之前,使用了Time.stub!(:now).and_return(2.days.ago)。这样,我就可以控制用于特定测试用例的时间


您的测试最终不会改变吗?有什么例子吗?
旅行

例如,如果我有一个租赁系统,我想测试如果退回商品的日期较晚,系统是否收取罚款费用,我需要操纵Time.now以便退回商品时晚了……不确定你是否能得到我
Staelen

12

使用Rspec 3.2,我发现伪造Time.now的唯一简单方法是返回值:

now = Time.parse("1969-07-20 20:17:40")
allow(Time).to receive(:now) { now }

现在,Time.now将始终返回阿波罗11号登月的日期。

资料来源:https : //www.relishapp.com/rspec/rspec-mocks/docs


很好的答案!到目前为止,这是最简单的解决方案,对于时间等基本的东西进行静态模拟似乎是一种合理的方法,因为最终会将所有基础库注入到您的类中。
亚历山大·奥

10

时间扭曲

时间扭曲是可以满足您需求的库。它为您提供了一个方法,该方法需要一个时间和一个块,并且块中发生的任何事情都将使用假时间。

pretend_now_is(2000,"jan",1,0) do
  Time.now
end

7

别忘了那Time只是一个引用类对象的常量。如果您愿意发出警告,则可以随时执行

real_time_class = Time
Time = FakeTimeClass
# run test
Time = real_time_class


1

也请参阅此问题,我也在此发表评论。

根据所比较Time.now的内容,有时您可以更改灯具以实现相同的目标或测试相同的功能。例如,我遇到这样的情况,如果某个日期是将来的某个日期,那么我需要做一件事;如果是过去的某个日期,则需要另一件事。我能够做的就是在我的装置中包括一些嵌入式红宝石(erb):

future:
    comparing_date: <%= Time.now + 10.years %>
    ...

past:
    comparing_date: <%= Time.now - 10.years %>
    ...

然后,在测试中,您将根据相对于的时间选择使用哪一个来测试不同的功能或动作Time.now


谢谢,我发现这比伪造Time.now更优雅。您的测试还将确保任何地方都没有硬编码的日期,并且您的代码在运行之日仍能按预期运行
Benoit 2016年

1

遇到同样的问题,我不得不在特定的日期里花时间制造规格,而时间就是那样:

Time.stub!(:now).and_return(Time.mktime(2014,10,22,5,35,28))        

这将为您提供:

2014-10-22 05:35:28 -0700

感叹号做了什么?这是使用摩卡宝石吗?
阿诺德·罗阿

实际上,我认为它stub!已被剥夺了,您stub现在就可以使用
Netta D

0

这种工作并允许嵌套:

class Time
  class << self
    attr_accessor :stack, :depth
  end

  def self.warp(time)

    Time.stack ||= [] 
    Time.depth ||= -1 
    Time.depth += 1
    Time.stack.push time

    if Time.depth == 0 
      class << self    
          alias_method :real_now, :now  
          alias_method :real_new, :new

          define_method :now do
            stack[depth] 
          end

          define_method :new do 
            now 
          end
      end 
    end 

    yield

    Time.depth -= 1
    Time.stack.pop 

    class << self 
      if Time.depth < 0 
        alias_method :new, :real_new
        alias_method :now, :real_now
        remove_method :real_new
        remove_method :real_now 
      end
    end

  end
end

可以通过在末尾未定义堆栈和深度访问器来对其进行稍微改进

用法:

time1 = 2.days.ago
time2 = 5.months.ago
Time.warp(time1) do 

  Time.real_now.should_not == Time.now

  Time.now.should == time1 
  Time.warp(time2) do 
    Time.now.should == time2
  end 
  Time.now.should == time1
end

Time.now.should_not == time1 
Time.now.should_not be_nil

0

根据所比较Time.now的内容,有时您可以更改灯具以实现相同的目标或测试相同的功能。例如,我遇到这样的情况,如果某个日期是将来的某个日期,那么我需要做一件事;如果是过去的某个日期,则需要另一件事。我能够做的就是在我的装置中包括一些嵌入式红宝石(erb):

future:
    comparing_date: <%= Time.now + 10.years %>
    ...

past:
    comparing_date: <%= Time.now - 10.years %>
    ...

然后,在测试中,您将根据相对于的时间选择使用哪一个来测试不同的功能或动作Time.now


0

我只是在我的测试文件中有这个:

   def time_right_now
      current_time = Time.parse("07/09/10 14:20")
      current_time = convert_time_to_utc(current_date)
      return current_time
    end

在我的Time_helper.rb文件中,我有一个

  def time_right_now
    current_time= Time.new
    return current_time
  end

因此,在测试time_right_now时,它会被覆盖以使用您希望的时间。


0

我总是将其提取Time.now到一个单独的方法中,然后将其转换attr_accessor为模拟方法。


0

即使不使用依赖注入方式重新构造代码,最新发布的代码也Test::Redef可以使此操作和其他伪造变得容易(特别是在使用其他人的代码的情况下特别有用)。

fake_time = Time.at(12345) # ~3:30pm UTC Jan 1 1970
Test::Redef.rd 'Time.now' => proc { fake_time } do
   assert_equal 12345, Time.now.to_i
end

但是,请注意其他获取时间的方法,以免伪造(Date.new一个编译的扩展程序,它会进行自己的系统调用,与知道当前时间戳的外部数据库服务器等接口,等等)。听起来像上面的Timecop库可能会克服这些限制。

其他伟大的用途包括测试诸如“当我尝试使用此友好的http客户端,但它决定引发此异常而不是返回一个字符串时会发生什么情况?”之类的事情。没有实际设置导致该异常的网络条件(这可能很棘手)。它还可以让您检查重新定义函数的参数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.