怎么伪造时间,现在?

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

28737 次浏览

时间扭曲

时间曲速是一个能做你想做的事情的图书馆。它提供了一个方法,该方法接受一个时间和一个块,块中发生的任何事情都使用假时间。

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

就我个人而言,我更喜欢把时钟做成可注射的,就像这样:

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

然而,许多人更喜欢使用一个模仿/存根库:

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

或者在摩卡:

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

我真的很喜欢 时空警察库,你可以用块的形式来进行时间扭曲(就像时间扭曲一样) :

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

我有一些 黄瓜的时间警察 给你的帮手。他们可以让你做这样的事情:

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"

这种工作方式允许嵌套:

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的内容,有时您可以更改您的 fixture 来实现相同的目标或测试相同的特性。例如,我遇到过这样一种情况: 如果某个约会是在未来发生的,那么我需要一件事情发生; 如果某个约会是在过去发生的,那么我需要另一件事情发生。我所能做的就是在我的夹具中包含一些嵌入式 Ruby (erb) :

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


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

然后在测试中,根据相对于 Time.now的时间,选择使用哪一个来测试不同的特性或操作。

也可以看看我把这个注释放在哪里的 这个问题

根据您比较 Time.now的内容,有时您可以更改您的 fixture 来实现相同的目标或测试相同的特性。例如,我遇到过这样一种情况: 如果某个约会是在未来发生的,那么我需要一件事情发生; 如果某个约会是在过去发生的,那么我需要另一件事情发生。我所能做的就是在我的夹具中包含一些嵌入式 Ruby (erb) :

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


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

然后在测试中,根据相对于 Time.now的时间,选择使用哪一个来测试不同的特性或操作。

我使用 RSpec 并且在调用 Time 之前执行: Stub! (: now) . and _ return (2. days. ago)。通过这种方式,我能够控制用于那个特定测试用例的时间

我的测试文件里有这个:

   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 时,它会被覆盖以使用您想要的任何时间。

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

不要忘记,Time仅仅是一个引用类对象的常量。如果你愿意提出警告,随时都可以

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

最近发布的 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 客户端但它决定引发这个异常而不是返回一个字符串时会发生什么?”没有实际设置导致异常的网络条件(这可能是棘手的)。它还允许您检查 redef’d 函数的参数。

遇到同样的问题,我不得不为了一个特定的日子而假造时间,时间就是这样做的:

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

这会给你:

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

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

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

现在时间总是返回阿波罗11号登月的日期。

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

如果包含 ActiveSupport,则可以使用:

travel_to Time.zone.parse('2010-07-05 08:00')

Http://api.rubyonrails.org/classes/activesupport/testing/timehelpers.html

我自己的解决方案 https://github.com/igorkasyanchuk/rails_time_travel-一个带 UI 的 gem,所以你不需要在代码中硬编码任何日期时间。从用户界面改一下。

对于 QA 的团队或者测试阶段的应用程序来说,它可能也非常有用。