web-dev-qa-db-ja.com

Time.nowを偽造する方法は?

単体テストで時間依存メソッドをテストする目的でTime.nowを設定する最良の方法は何ですか?

70
user147962

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"
74
James A. Rosen

個人的には、次のようにクロックを注入可能にすることを好みます:

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

私はRSpecを使用していますが、これを行いました:Time.stub!(:now).and_return(2.days.ago) Time.nowを呼び出す前に。そのようにして、特定のテストケースに使用した時間を制御できます。

14
Staelen

time-warp を実行します

タイムワープは、あなたが望むことをするライブラリです。それはあなたに時間とブロックを要する方法を与え、ブロックで起こることはすべて偽造された時間を使用します。

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

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
Iwazaru

Timeは単にクラスオブジェクトを参照する定数であることを忘れないでください。警告を発生させたい場合は、いつでも行うことができます

real_time_class = Time
Time = FakeTimeClass
# run test
Time = real_time_class
7
Andrew Grimm

this question も参照してください。ここでもこのコメントを掲載しています。

Time.nowと比較する対象によっては、フィクスチャを変更して同じ目標を達成したり、同じ機能をテストしたりできる場合があります。たとえば、ある日付が未来の場合に発生することと、過去の場合に発生することの両方が必要な状況がありました。私ができることは、フィクスチャに埋め込みのRuby(erb):

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

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

次に、テストで、Time.nowに関連する時間に基づいて、異なる機能またはアクションをテストするために使用するものを選択します。

1
Bryan Ward

ActiveSupportが含まれている場合は、次を使用できます。

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

http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html

1
Artur Beljajev

同じ問題を抱えていたので、特定の日のために仕様の時間を偽造しなければなりませんでした。

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

これはあなたに与えます:

2014-10-22 05:35:28 -0700
1
Netta D

この種の作品とネストを可能にします:

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
Sam Saffron

私は私のテストファイルにこれを持っています:

   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
Mo.

私は常にTime.nowを別のメソッドに抽出し、それをモックでattr_accessorにします。

0
hakunin

最近リリースされた Test::Redef は、依存関係注入スタイルでコードを再構築しなくても、このおよびその他のfakeryを簡単にします(特に、他の人々のコード。)

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クライアントを使用しようとしているが、文字列を返さずに例外を発生させるとどうなるか」などのテストが含まれます。その例外につながるネットワーク条件を実際に設定することはありません(注意が必要な場合があります)。また、再定義された関数の引数を確認できます。

0
fennec

Time.nowと比較する対象によっては、フィクスチャを変更して同じ目標を達成したり、同じ機能をテストしたりできる場合があります。たとえば、ある日付が未来の場合に発生することと、過去の場合に発生することの両方が必要な状況がありました。私ができることは、フィクスチャに埋め込みのRuby(erb):

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

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

次に、テストで、Time.nowに関連する時間に基づいて、異なる機能またはアクションをテストするために使用するものを選択します。

0
Bryan Ward