Quelle est la meilleure façon de définir Time.now
pour tester des méthodes sensibles au temps dans un test unitaire?
J'aime beaucoup la bibliothèque Timecop . Vous pouvez faire des warps temporels sous forme de bloc (comme pour time-warp):
Timecop.travel(6.days.ago) do
@model = TimeSensitiveMode.new
end
assert @model.times_up!
(Oui, vous pouvez imbriquer un voyage dans le temps sous forme de bloc.)
Vous pouvez également faire un voyage dans le temps déclaratif:
class MyTest < Test::Unit::TestCase
def setup
Timecop.travel(...)
end
def teardown
Timecop.return
end
end
J'ai quelques concombre helpers pour Timecop ici . Ils vous ont laissé faire des choses comme:
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"
Personnellement, je préfère rendre l'horloge injectable, comme ceci:
def hello(clock=Time)
puts "the time is now: #{clock.now}"
end
Ou:
class MyClass
attr_writer :clock
def initialize
@clock = Time
end
def hello
puts "the time is now: #{@clock.now}"
end
end
Cependant, beaucoup préfèrent utiliser une bibliothèque de moquage. Dans RSpec/flexmock, vous pouvez utiliser:
Time.stub!(:now).and_return(Time.mktime(1970,1,1))
Ou en moka:
Time.stubs(:now).returns(Time.mktime(1970,1,1))
J'utilise RSpec et j'ai fait ceci: Time.stub! (: Now) .and_return (2.days.ago) avant d'appeler Time.now. De cette façon, je suis en mesure de contrôler le temps que j'ai utilisé pour ce cas de test particulier
Faites le time-warp
time-Warp est une bibliothèque qui fait ce que vous voulez. Il vous donne une méthode qui prend un temps et un bloc et tout ce qui se produit dans le bloc utilise le temps simulé.
pretend_now_is(2000,"jan",1,0) do
Time.now
end
En utilisant Rspec 3.2, le seul moyen simple que j'ai trouvé de simuler une valeur de retour Time.now est:
now = Time.parse("1969-07-20 20:17:40")
allow(Time).to receive(:now) { now }
Maintenant, Time.now renverra toujours la date d’atterrissage d’Apollo 11 sur la lune.
N'oubliez pas que Time
est simplement une constante qui fait référence à un objet de classe. Si vous êtes prêt à donner un avertissement, vous pouvez toujours le faire.
real_time_class = Time
Time = FakeTimeClass
# run test
Time = real_time_class
Voir aussi cette question où je mets aussi ce commentaire.
En fonction de ce que vous comparez à Time.now
, vous pouvez parfois changer vos appareils pour atteindre le même objectif ou tester la même fonctionnalité. Par exemple, je me trouvais dans une situation où il me fallait une chose si quelque date était future et une autre si c'était dans le passé. Ce que j'ai pu faire, c'est d'inclure dans mes appareils un Ruby intégré (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
Ensuite, dans vos tests, vous choisissez lequel utiliser pour tester les différentes fonctionnalités ou actions en fonction du temps par rapport à Time.now
.
Si j'avais le même problème, je devais simuler du temps pour une spécification pour un jour et une heure spécifiques, mais je viens de le faire
Time.stub!(:now).and_return(Time.mktime(2014,10,22,5,35,28))
cela vous donnera:
2014-10-22 05:35:28 -0700
Ce type de travaux permet et permet d'imbriquer:
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
Il pourrait être légèrement amélioré en décompressant les accessoires de pile et de profondeur à la fin.
Usage:
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
j'ai juste cela dans mon fichier de test:
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
et dans mon fichier Time_helper.rb j'ai un
def time_right_now
current_time= Time.new
return current_time
end
ainsi, lors du test, time_right_now est écrasé pour utiliser le temps que vous souhaitez
Si ActiveSupport est inclus, vous pouvez utiliser http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html
J'extrais toujours Time.now
dans une méthode distincte que je transforme en attr_accessor
dans la maquette.
Le Test::Redef
publié récemment rend cette et toute autre falsification facile, même sans restructurer le code dans un style d'injection de dépendances (particulièrement utile si vous utilisez le code d'autres personnes.)
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
Cependant, faites attention aux autres moyens d'obtenir du temps sans fausse impression (Date.new
, une extension compilée qui effectue son propre appel système, des interfaces avec des serveurs de base de données externes qui connaissent les horodatages actuels, etc.). Cela ressemble à la bibliothèque Timecop. ci-dessus pourrait surmonter ces limitations.
Parmi les autres utilisations intéressantes, citons le test suivant: "que se passe-t-il lorsque j'essaie d'utiliser ce client HTTP convivial, mais il décide de créer une exception au lieu de me retourner une chaîne?" sans définir réellement les conditions de réseau qui conduisent à cette exception (ce qui peut être délicat). Il vous permet également de vérifier les arguments pour redéfinir les fonctions.
En fonction de ce que vous comparez à Time.now
, vous pouvez parfois changer vos appareils pour atteindre le même objectif ou tester la même fonctionnalité. Par exemple, je me trouvais dans une situation où il me fallait une chose si quelque date était future et une autre si c'était dans le passé. Ce que j'ai pu faire, c'est d'inclure dans mes appareils un Ruby intégré (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
Ensuite, dans vos tests, vous choisissez lequel utiliser pour tester les différentes fonctionnalités ou actions en fonction du temps par rapport à Time.now
.