2009-07-31 10 views
67

Was ist der beste Weg, Time.now für den Test von zeitempfindlichen Methoden in einem Komponententest einzustellen?Wie man Time.now vortäuscht?

+6

Gibt es ein Timelord Ruby Gem? : P – Rob

+3

Schließen! Da ist Timecop. (Siehe meine Antwort unten.) –

Antwort

10

die

Zeitverzerrungs time-warp Sie ist eine Bibliothek, das tut, was Sie wollen. Es gibt Ihnen eine Methode, die eine Zeit und einen Block benötigt und alles, was im Block passiert, verwendet die gefälschte Zeit.

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

Persönlich bevorzuge ich die Uhr injizierbare zu machen, etwa so:

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

Oder:

class MyClass 
    attr_writer :clock 

    def initialize 
    @clock = Time 
    end 

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

jedoch viele bevorzugen eine spöttische/stubbing Bibliothek zu verwenden. In RSpec/flexmock können Sie:

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

Oder in Mokka:

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

Der Testteil ist genau die Art von Antwort, die ich liefern würde. – nitecoder

+0

Das wäre genau das, was ich auch getan hätte. Wenn Sie Ihren Code so schreiben, dass er leicht zu testen ist, müssen Sie keine obskuren Dinge tun, um ihn testbar zu machen. –

+5

Während ich der generellen Idee zustimme, Zeug auf eine überprüfbarere Weise zu schreiben, gibt es Zeiten, in denen es einfach nicht praktikabel ist. Und Time.now ist eines dieser Beispiele. es kann in vielen Teilen des Systems verwendet werden und es die ganze Zeit herumzugeben wird zu viel Overhead sein. –

71

Ich mag die Timecop Bibliothek. Sie können Zeit Warps in Blockform (wie Zeit-Warp) tun:

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

(Ja, können Sie nisten in Blockform eine Zeitreise.)

Sie auch Reise tun deklarative Zeit kann:

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

Ich habe einige cucumber Helfer für Timecop here. Sie lassen Sie Dinge tun, wie:

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" 
+0

Sollte am Ende des letzten Tests ein Anführungszeichen stehen? –

+0

Diese Helfer wurden in ein Juwel gelegt: https://github.com/zedtux/cucumber-timecop – approxiblue

0

Diese Art der Arbeiten und für die Verschachtelung erlaubt:

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 

Es leicht um undefing die Stapel und Tiefe Accessoren am Ende

Verbrauch verbessert werden könnte :

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

Je nachdem, was Sie Time.now zu vergleichen, manchmal yo Sie können Ihre Fixtures ändern, um das gleiche Ziel zu erreichen oder die gleiche Funktion zu testen. Zum Beispiel hatte ich eine Situation, in der ich eine Sache brauchte, wenn ein Datum in der Zukunft war und ein anderes, wenn es in der Vergangenheit war. Was konnte ich war tun, ist in meinen Vorrichtungen einig eingebetteten Rubin (erb):

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

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

Dann in Ihren Tests Sie dann wählen, welche zu verwenden, um die verschiedenen Funktionen oder Aktionen von der Zeit in Bezug auf Time.now Basis zu testen .

1

Siehe auch this question, wo ich diesen Kommentar auch setzen.

Je nachdem, was Sie Time.now vergleichen, können Sie manchmal Ihre Fixtures ändern, um das gleiche Ziel zu erreichen oder die gleiche Funktion zu testen.Zum Beispiel hatte ich eine Situation, in der ich eine Sache brauchte, wenn ein Datum in der Zukunft war und ein anderes, wenn es in der Vergangenheit war. Was konnte ich war tun, ist in meinen Vorrichtungen einig eingebetteten Rubin (erb):

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

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

Dann in Ihren Tests Sie dann wählen, welche zu verwenden, um die verschiedenen Funktionen oder Aktionen von der Zeit in Bezug auf Time.now Basis zu testen .

+0

Danke, ich fand das viel eleganter als das Fälschen von Time.now. Ihre Tests stellen auch sicher, dass es nirgends ein festes Datum gibt und Ihr Code funktioniert immer noch wie erwartet am Tag der Ausführung. – Benoit

14

Ich verwende RSpec und ich tat dies: Time.stub! (: Jetzt) ​​.and_return (2.days.ago) bevor ich Time.now anrufe. Auf dieser Art und Weise die Zeit, die ich für diesen speziellen Testfall

+0

Würden sich Ihre Tests eventuell nicht ändern? Haben Sie Beispiele? – Trip

+0

für z.B. Wenn ich ein Mietsystem habe, möchte ich testen, ob das System Strafgebühren verlangt, wenn der Tag, an dem der Gegenstand zurückgegeben wird, verspätet ist. Ich muss Time.now manipulieren, damit der Artikel zurückkommt, wenn er zurückkommt. .. nicht sicher, ob Sie mich bekommen – Staelen

0

ich dies nur haben, das zur Kontrolle Ich bin in der Lage in meiner Test-Datei:

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 

und in meiner Time_helper.rb Datei habe ich ein

def time_right_now 
    current_time= Time.new 
    return current_time 
    end 

Also beim Testen wird die time_right_now überschrieben, um zu verwenden, was immer Sie wollen, es zu sein.

0

Ich extrahiere immer Time.now in eine separate Methode, die ich im Schein in attr_accessor verwandeln.

7

Vergessen Sie nicht, dass Time lediglich eine Konstante ist, die sich auf ein Klassenobjekt bezieht. Wenn Sie bereit sind, eine Warnung zu verursachen, können Sie immer tun

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

Die kürzlich veröffentlichten Test::Redef macht diese und anderen fakery einfach, auch ohne den Code in einer Abhängigkeitsinjektion Stil Umstrukturierung (besonders hilfreich, wenn Sie verwenden anderer Leute Code.)

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 

jedoch andere Möglichkeiten vorsichtig sein, Zeit zu erhalten, dass dies nicht fälschen aus (Date.new, eine kompilierte Erweiterung, die ihr eigenes System Anruf tätigt, Schnittstellen, um Dinge wie externe Datenbankserver, die den aktuellen Zeitstempel kennen ps, etc.) Es scheint, als ob die Timecop-Bibliothek diese Einschränkungen überwinden könnte.

Andere große Anwendungen schließen das Testen von Dingen wie "Was passiert, wenn ich versuche, diesen freundlichen HTTP-Client zu verwenden, aber es entscheidet, eine Ausnahme auszulösen, anstatt mir eine Zeichenfolge zurückzugeben?" ohne tatsächlich die Netzwerkbedingungen einzurichten, die zu dieser Ausnahme führen (was schwierig sein kann). Außerdem können Sie die Argumente für neu definierte Funktionen überprüfen.

1

hat das gleiche Problem, ich für eine Spezifikation für einen bestimmten Tag und Zeit nur zu fälschen Zeit gehabt hat, dass:

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

das dir geben:

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

was das Ausrufezeichen getan? Dies ist mit Mokka-Edelstein? –

+0

eigentlich denke ich 'stub!' Ist entzogen und Sie können einfach 'stub' jetzt verwenden –

7

Mit Rspec 3.2, wenn die einzige einfache Art und Weise zu fälschen Time.now Rückgabewert gefunden ist:

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

Jetzt Time.now wird immer wieder zurückkehrt das Datum der Apol lo 11 Landung auf dem Mond.

Quelle: https://www.relishapp.com/rspec/rspec-mocks/docs

Verwandte Themen