Was ist der beste Weg, Time.now
einzustellen, um zeitempfindliche Methoden in einem Komponententest zu testen?
Ich mag die Timecop - Bibliothek wirklich. Sie können Zeitverzerrungen in Blockform durchführen (genau wie Zeitverzerrungen):
Timecop.travel(6.days.ago) do
@model = TimeSensitiveMode.new
end
assert @model.times_up!
(Ja, Sie können Blockzeit-Zeitreisen verschachteln.)
Sie können auch deklarative Zeitreisen durchführen:
class MyTest < Test::Unit::TestCase
def setup
Timecop.travel(...)
end
def teardown
Timecop.return
end
end
Ich habe einige Gurken Helfer für Timecop hier . 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"
Ich persönlich bevorzuge die Uhr injizierbar zu machen:
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
Viele ziehen es jedoch vor, eine spöttische Bibliothek zu verwenden. In RSpec/Flexmock können Sie Folgendes verwenden:
Time.stub!(:now).and_return(Time.mktime(1970,1,1))
Oder in Mocha:
Time.stubs(:now).returns(Time.mktime(1970,1,1))
Ich benutze RSpec und habe folgendes gemacht: Time.stub! (: Now) .and_return (2.days.ago) bevor ich Time.now anrufe. Auf diese Weise kann ich die Zeit steuern, die ich für diesen bestimmten Testfall verwendet habe
Machen Sie die Zeitverzerrung
time-Warp ist eine Bibliothek, die macht, was Sie wollen. Es gibt eine Methode, die eine Zeit und einen Block benötigt, und alles, was in dem Block passiert, verwendet die gefälschte Zeit.
pretend_now_is(2000,"jan",1,0) do
Time.now
end
Bei Verwendung von Rspec 3.2 habe ich den einzigen einfachen Weg gefunden, den Time.now-Rückgabewert zu fälschen:
now = Time.parse("1969-07-20 20:17:40")
allow(Time).to receive(:now) { now }
Jetzt gibt Time.now immer das Datum der Landung von Apollo 11 auf dem Mond zurück.
Vergessen Sie nicht, dass Time
nur eine Konstante ist, die auf ein Klassenobjekt verweist. Wenn Sie eine Warnung auslösen möchten, können Sie dies jederzeit tun
real_time_class = Time
Time = FakeTimeClass
# run test
Time = real_time_class
Siehe auch diese Frage wo ich diesen Kommentar auch platziere.
Je nachdem, was Sie mit Time.now
vergleichen, können Sie manchmal Ihre Fixtures ändern, um dasselbe Ziel zu erreichen, oder das gleiche Feature testen. Ich hatte zum Beispiel eine Situation, in der ich eine Sache brauchte, wenn ein Datum in der Zukunft lag und eine andere, wenn es in der Vergangenheit lag. Was ich konnte, war eingebettetes Ruby (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
In Ihren Tests wählen Sie dann aus, welche der verschiedenen Funktionen oder Aktionen basierend auf der Zeit relativ zu Time.now
getestet werden soll.
Hatte das gleiche Problem, ich musste die Zeit für eine Spezifikation für einen bestimmten Tag fälschen, und die Zeit tat genau das:
Time.stub!(:now).and_return(Time.mktime(2014,10,22,5,35,28))
das wird dir geben:
2014-10-22 05:35:28 -0700
Diese Art von Arbeiten und ermöglicht das Schachteln:
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 könnte etwas verbessert werden, indem am Ende die Stapel- und Tiefenzugriffe entfernt werden
Verwendungszweck:
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
ich habe dies nur in meiner Testdatei:
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 eine
def time_right_now
current_time= Time.new
return current_time
end
beim Testen wird time_right_now überschrieben, um die Uhrzeit zu verwenden, die Sie möchten.
Wenn Sie ActiveSupport verwenden, können Sie http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html verwenden.
Ich extrahiere Time.now
immer in eine separate Methode, die ich im Mock in attr_accessor
umwandle.
Der kürzlich veröffentlichte Test::Redef
macht dieses und andere Fake einfach, auch ohne den Code in einem Abhängigkeits-Injektionsstil umzugestalten (besonders hilfreich, wenn Sie den Code anderer Benutzer verwenden.)
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
Achten Sie jedoch auf andere Möglichkeiten, um Zeit zu erhalten, die nicht verfälscht wird (Date.new
, eine kompilierte Erweiterung, die ein eigenes System aufruft, Schnittstellen zu Dingen wie externen Datenbankservern, die aktuelle Zeitstempel kennen usw.). Es klingt wie die Timecop-Bibliothek oben könnten diese Einschränkungen überwinden.
Andere großartige Anwendungen umfassen das Testen von Dingen wie "Was passiert, wenn ich versuche, diesen benutzerfreundlichen http-Client zu verwenden, aber er beschließt, diese Ausnahme auszulösen, anstatt mir eine Zeichenfolge zurückzugeben?" ohne die Netzbedingungen festzulegen, die zu dieser Ausnahme führen (was schwierig sein kann). Außerdem können Sie die Argumente überprüfen, um Funktionen neu zu definieren.
Je nachdem, was Sie mit Time.now
vergleichen, können Sie manchmal Ihre Fixtures ändern, um dasselbe Ziel zu erreichen, oder das gleiche Feature testen. Ich hatte zum Beispiel eine Situation, in der ich eine Sache brauchte, wenn ein Datum in der Zukunft lag und eine andere, wenn es in der Vergangenheit lag. Was ich konnte, war eingebettetes Ruby (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
In Ihren Tests wählen Sie dann aus, welche der verschiedenen Funktionen oder Aktionen basierend auf der Zeit relativ zu Time.now
getestet werden soll.