ruby - Cómo falsificar Time.now?
unit-testing (14)
Acabo de tener esto en mi archivo de prueba:
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
y en mi archivo Time_helper.rb tengo un
def time_right_now
current_time= Time.new
return current_time
end
así que cuando se prueba el time_right_now se sobrescribe para usar el tiempo que desee.
¿Cuál es la mejor manera de configurar Time.now
con el fin de probar métodos sensibles al tiempo en una prueba unitaria?
Dependiendo de lo que esté comparando Time.now
, a veces puede cambiar sus dispositivos para lograr el mismo objetivo o probar la misma función. Por ejemplo, tuve una situación en la que necesitaba que sucediera algo si alguna fecha estaba en el futuro y otra que sucedería si era en el pasado. Lo que pude hacer fue incluir en mis aparatos algunos rubíes incrustados (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
Luego, en sus pruebas, usted elige cuál usar para probar las diferentes funciones o acciones en función del tiempo relativo a Time.now
.
El Test::Redef
reciente Test::Redef
facilita esta y otras imposturas , incluso sin reestructurar el código en un estilo de inyección de dependencia (especialmente útil si está usando el código de otras personas).
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
Sin embargo, tenga cuidado con otras formas de obtener tiempo que esto no Date.new
( Date.new
, una extensión compilada que hace su propia llamada al sistema, interfaces con cosas como servidores de bases de datos externas que conocen las marcas de tiempo actuales, etc.) Parece que La biblioteca Timecop anterior puede superar estas limitaciones.
Otros grandes usos incluyen probar cosas como "¿qué sucede cuando trato de usar este amigable cliente http, pero decide plantear esta excepción en lugar de devolverme una cadena?" sin configurar realmente las condiciones de la red que conducen a esa excepción (que puede ser complicado). También le permite verificar los argumentos para redefinir las funciones.
Este tipo de trabajo y permite la anidación:
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
Se podría mejorar ligeramente al indefinir los accesorios de pila y profundidad al final
Uso:
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
Estoy usando RSpec y lo hice: Time.stub! (: Now) .and_return (2.days.ago) antes de llamar a Time.now. De esa forma, puedo controlar el tiempo que utilicé para ese caso de prueba en particular
Hacer el time-warp
time-warp es una biblioteca que hace lo que quieres. Te da un método que toma un tiempo y un bloque y todo lo que sucede en el bloque usa el tiempo falso.
pretend_now_is(2000,"jan",1,0) do
Time.now
end
Me gusta mucho la biblioteca Timecop . Puedes hacer time warps en forma de bloque (al igual que time-warp):
Timecop.travel(6.days.ago) do
@model = TimeSensitiveMode.new
end
assert @model.times_up!
(Sí, puedes anidar el viaje en el tiempo en forma de bloque).
También puede hacer un viaje declarativo en el tiempo:
class MyTest < Test::Unit::TestCase
def setup
Timecop.travel(...)
end
def teardown
Timecop.return
end
end
Tengo algunos ayudantes de cucumber para Timecop here . Te dejan hacer cosas como estas:
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"
No olvide que el Time
es simplemente una constante que se refiere a un objeto de clase. Si está dispuesto a provocar una advertencia, siempre puede hacer
real_time_class = Time
Time = FakeTimeClass
# run test
Time = real_time_class
Personalmente prefiero hacer el reloj inyectable, así:
def hello(clock=Time)
puts "the time is now: #{clock.now}"
end
O:
class MyClass
attr_writer :clock
def initialize
@clock = Time
end
def hello
puts "the time is now: #{@clock.now}"
end
end
Sin embargo, muchos prefieren usar una biblioteca de burla / stubbing. En RSpec / flexmock puedes usar:
Time.stub!(:now).and_return(Time.mktime(1970,1,1))
O en Mocha:
Time.stubs(:now).returns(Time.mktime(1970,1,1))
Si tiene ActiveSupport incluido, puede usar http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html
Siempre Time.now
en un método separado que convierto en attr_accessor
en el simulacro.
También vea esta pregunta donde puse este comentario también.
Dependiendo de lo que esté comparando Time.now
, a veces puede cambiar sus dispositivos para lograr el mismo objetivo o probar la misma función. Por ejemplo, tuve una situación en la que necesitaba que sucediera algo si alguna fecha estaba en el futuro y otra que sucedería si era en el pasado. Lo que pude hacer fue incluir en mis aparatos algunos rubíes incrustados (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
Luego, en sus pruebas, usted elige cuál usar para probar las diferentes funciones o acciones en función del tiempo relativo a Time.now
.
Tuve el mismo problema, tuve que simular tiempo para una especificación para un día específico y solo lo hice:
Time.stub!(:now).and_return(Time.mktime(2014,10,22,5,35,28))
esto te dará:
2014-10-22 05:35:28 -0700
Usando Rspec 3.2, la única manera simple si se encuentra para falsificar el valor de retorno de Time.now es:
now = Time.parse("1969-07-20 20:17:40")
allow(Time).to receive(:now) { now }
Ahora Time.now siempre devolverá la fecha de aterrizaje del Apolo 11 en la luna.