如何伪造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条件(这可能是棘手的)。 它也可以让你检查重新函数的参数。
如果你有ActiveSupport,你可以使用http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html