2009-07-31 20 views
67

¿Cuál es la mejor manera de establecer Time.now con el fin de probar métodos sensibles al tiempo en una prueba unitaria?Cómo falsificar Time.now?

+6

¿Hay una joya timelord rubí? : P – Rob

+3

¡Cerrar! Hay Timecop. (Consulte mi respuesta a continuación.) –

Respuesta

10

Haga lo time-warp

tiempo de cápsula es una biblioteca que hace lo que quiere. 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 
41

Personalmente prefiero hacer el inyectable de reloj, 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/stubbing burla. En RSpec/flexmock puede utilizar:

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

O en Mocha:

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

La parte de prueba es exactamente el tipo de respuesta que iba a proporcionar. – nitecoder

+0

Esto es totalmente lo que habría hecho también. Si escribe su código de una manera que lo haga fácil de probar, no tiene que hacer cosas arcanas para hacerlo comprobable. –

+5

Si bien estoy de acuerdo con la idea general de escribir cosas de una manera más comprobable, hay ocasiones en que simplemente no es práctico. Y Time.now es uno de esos ejemplos. se puede usar en muchas partes del sistema y pasarlo todo el tiempo será demasiado alto. –

71

me gusta mucho la biblioteca Timecop. Usted puede hacer el tiempo deforma en forma de bloques (como tiempo de cápsula):

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

(Sí, puede anidar viaje en el tiempo en forma de bloque.)

También se puede hacer viajes en el tiempo declarativa:

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

Tengo algunos ayudantes cucumber para Timecop here. Ellos le permiten hacer cosas como:

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

¿Debería haber una comilla al final de la última prueba? –

+0

Estos ayudantes se han puesto en una gema: https://github.com/zedtux/cucumber-timecop – approxiblue

0

Este tipo de obras 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 

Podría ser ligeramente mejorada por undefing los descriptores de acceso 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 
0

Dependiendo de lo que compare Time.now, a veces yo Puedes cambiar tus accesorios 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 accesorios de algunos de rubí incrustado (erb):

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

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

Luego, en sus pruebas y luego a elegir cuál usar para probar las diferentes funciones o acciones basadas en el tiempo en relación con Time.now .

1

También vea this question donde pongo este comentario también.

Según 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 accesorios de algunos de rubí incrustado (erb):

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

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

Luego, en sus pruebas y luego a elegir cuál usar para probar las diferentes funciones o acciones basadas en el tiempo en relación con Time.now .

+0

Gracias, encontré esto mucho más elegante que falsificar Time.now. Sus pruebas también asegurarán que no haya ninguna fecha de codificación fija en ninguna parte y su código aún funciona como se espera el día que lo ejecute – Benoit

14

Estoy usando RSpec y lo hice: Time.stub! (: Ahora) .and_return (2.days.ago) antes de que llame a Time.now. De ese modo soy capaz de controlar el tiempo que he usado para este caso prueba en particular

+0

¿No cambiarían sus pruebas con el tiempo? ¿Tienes algún ejemplo? – Trip

+0

por ej. si tengo un sistema de alquiler, quiero probar si el sistema cobra una multa si el día en que se devuelve el artículo es tarde, tendré que manipular Time.now para que cuando se devuelva el artículo, sea tarde. .. no estoy seguro de si me entiendes – Staelen

0

acabo de 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 la hora que desee.

0

Siempre extraigo Time.now en un método separado que me convierto en attr_accessor en el simulacro.

7

No olvide que Time es simplemente una constante que hace referencia a un objeto de clase. Si usted está dispuesto a causar una advertencia, siempre se puede hacer

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

El recientemente lanzado Test::Redef hace que este y otra falsedad fácil, incluso sin tener que reestructurar el código en un estilo de dependencia de la inyección (especialmente útil si usted está utilizando 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 de otras formas de obtener el tiempo de que esto no fuera falsa (Date.new, una extensión compilado que hace su propia llamada al sistema, interfaces con cosas como servidores de bases de datos externas que conocen el tiempo actual ps, etc.) Parece que la biblioteca de Timecop anterior podría 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.

1

tenían el mismo problema, que tenía en cuando falsa para una especificación para un día y hora específicos acaba de hacer lo siguiente:

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

esto le dará:

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

¿qué es el signo de exclamación? esto es usando la gema de moka? –

+0

, de hecho, creo que 'stub!' Está privado y puedes simplemente usar 'stub' ahora –

7

Usando Rspec 3.2, la única manera sencilla si se determina que el valor de retorno es falsa Time.now:

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

Ahora Time.now siempre devolverá la fecha de Apol Lo 11 aterrizando en la luna.

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

Cuestiones relacionadas