如何伪造Time.now?

为了在unit testing中testing时间敏感的方法,设置Time.now最好的方法是什么?

我真的很喜欢Timecop图书馆。 你可以以块的forms做时间扭曲(就像时间扭曲一样):

 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" 

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

 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)) 

我正在使用RSpec,而我在调用Time.now之前做了这个: Time.stub!(:now).and_return(2.days.ago) 。 通过这种方式,我可以控制用于特定testing用例的时间

做时间扭曲

时间扭曲是一个图书馆,做你想做的。 它给了你一个花费时间和块的方法,块中发生的任何事情都使用假时间。

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

不要忘记, Time只是一个指向类对象的常量。 如果你愿意发出警告,你可以随时做

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

使用Rspec 3.2,如果发现假的唯一简单的方法是Time.now,返回值是:

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

现在,“时光飞逝”将永远返回阿波罗11登陆月球的date。

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

也看到这个问题 ,我也把这个评论。

根据您比较Time.now的内容,有时您可以更改灯具以实现相同的目标或testing相同的function。 例如,我有一种情况,如果某个date是将来的,而另一个是在过去的时间,那么我需要一件事情发生。 我能做的是在我的设备中包含一些embedded式ruby(erb):

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

然后在你的testing中,你可以根据相对于Time.now的时间select使用哪一个来testing不同的特性或者动作。

有同样的问题,我不得不在一个特定的日子和时间虚假的时间只是这样做:

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

这会给你:

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

这种作品,并允许嵌套:

 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 

根据您比较Time.now的内容,有时您可以更改灯具以实现相同的目标或testing相同的function。 例如,我有一种情况,如果某个date是将来的,而另一个是在过去的时间,那么我需要一件事情发生。 我能做的是在我的设备中包含一些embedded式ruby(erb):

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

然后在你的testing中,你可以根据相对于Time.now的时间select使用哪一个来testing不同的特性或者动作。

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

  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 

所以当testingtime_right_now被覆盖以使用任何你想要它的时间。

我总是把Time.now提取到一个单独的方法中,在模拟中我把它变成了attr_accessor

最近发布的Test::Redef使得这个和其他的 Test::Redef变得容易,即使不用dependency injection的风格来重构代码(如果你使用其他人的代码,这个特别有用)。

 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库可能会克服这些限制。

其他很好的用途包括testing诸如“当我试图使用这个友好的http客户端时会发生什么,但是它决定提出这个exception而不是返回一个string? 而没有真正设置导致该exception的networking条件(这可能是棘手的)。 它也可以让你检查重新函数的参数。