2010-08-24 13 views
22

estoy trabajando con el tiempo en los carriles y usando el siguiente código para establecer la fecha de inicio y final de un proyecto:inconsistencias rieles de tiempo con rspec

start_date ||= Time.now 
end_date = start_date + goal_months.months 

entonces clonar el objeto y estoy escribiendo pruebas rspec para confirmar que los atributos coinciden en la copia. Las fechas finales coinciden:

original[end_date]: 2011-08-24 18:24:53 UTC 
clone[end_date]:  2011-08-24 18:24:53 UTC 

pero la especificación me da un error en las fechas de inicio:

expected: Wed Aug 24 18:24:53 UTC 2011, 
    got: Wed, 24 Aug 2011 18:24:53 UTC +00:00 (using ==) 

Está claro las fechas son el mismo, sólo un formato diferente. ¿Cómo es que acaban siendo almacenados de manera diferente en la base de datos, y cómo logro que coincidan? Lo he intentado con DateTime también con los mismos resultados.

Corrección: Las fechas de finalización no coinciden. Imprimen lo mismo, pero también errores de rspec en ellos. Al imprimir la fecha de fecha de inicio y fin, los valores vienen en diferentes formatos:

start date: 2010-08-24T19:00:24+00:00 
end date: 2011-08-24 19:00:24 UTC 

Respuesta

23

Debe simular el método now de Time para asegurarse de que siempre coincida con la fecha en la especificación. Nunca se sabe cuando un retraso hará que la especificación falle debido a algunos milisegundos. Este enfoque también asegurará que la hora en el código real y en la especificación sea la misma.

Si utiliza el valor por defecto rspec lib simulacro, tratar de hacer algo como:

t = Time.parse("01/01/2010 10:00") 
Time.should_receive(:now).and_return(t) 
+0

Muy buena respuesta! – lucapette

+8

También vea Timecop: https://github.com/travisjeffery/timecop – Kris

0

Mi conjetura inicial sería que el valor de Time.now tiene un formato distinto de su valor de base de datos.

0

¿Seguro que está utilizando == y no eql o be? Los dos últimos métodos usan identidad de objeto en lugar de comparar valores.

Desde la salida, parece que el valor esperado es Time, mientras que el valor que se prueba es DateTime. Esto también podría ser un problema, aunque dudaría en adivinar cómo solucionarlo dada la naturaleza casi patológica de las bibliotecas de fecha y hora de Ruby ...

+0

Sí, definitivamente usando ==. – lobati

+0

¿Obtiene el mismo resultado cuando usa DateTime en lugar de Time? – zetetic

50

Esto suele ocurrir porque rspec intenta hacer coincidir diferentes objetos: Hora y Fecha y hora , por ejemplo. Además, los tiempos comparables pueden diferir un poco, durante unos pocos milisegundos.

En el segundo caso, la forma correcta es utilizar el punteado y el simulacro. También vea TimeCop gem

En el primer caso, la posible solución puede ser comparar las marcas de tiempo:

actual_time.to_i.should == expected_time.to_i 

utilizo sencilla matcher para estos casos:

# ./spec/spec_helper.rb 
# 
# Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f} 
# 
# 
# Usage: 
# 
# its(:updated_at) { should be_the_same_time_as updated_at } 
# 
# 
# Will pass or fail with message like: 
# 
# Failure/Error: its(:updated_at) { should be_the_same_time_as 2.days.ago } 
# expected Tue, 07 Jun 2011 16:14:09 +0300 to be the same time as Mon, 06 Jun 2011 13:14:09 UTC +00:00 


RSpec::Matchers.define :be_the_same_time_as do |expected| 
    match do |actual| 
    expected.to_i == actual.to_i 
    end 
end 
+2

Recibo este problema INCLUSO cuando uso 'Timecop', que es realmente extraño. Ideas? – thekingoftruth

+0

Resulta que esto solo estaba fallando en nuestro entorno de integración continua. Parece que es un problema UTC vs UTC, así que voy a cambiar mi especificación a 'order.updated_at.utc.should == Time.now.utc', que debería arreglarlo. – thekingoftruth

+0

Mi solución no parece funcionar. Nuestro entorno de CI todavía dice: Diff: 2012-11-19 23:54:24 UTC. == (2012-11-19 23:54:24 UTC) devolvió falso aunque la diferencia entre 2012-11-19 23:54:24 UTC y 2012-11-19 23:54:24 UTC está vacío. Verifique la implementación de 2012-11-19 23:54:24 UTC. ==. – thekingoftruth

6

Uno de gotcha es que los objetos de Ruby tiempo han precisión de nanosegundos, pero la mayoría de las bases de datos tienen una precisión de microsegundos máxima. La mejor forma de evitar esto es utilizar Time.now (o use timecop) con un número redondo.Lea la publicación que escribí sobre esto: http://blog.solanolabs.com/rails-time-comparisons-devil-details-etc/

+0

Ha, I acabo de venir aquí para agregar este enlace después de tropezar con él. Terminé envolviendo Timecop en un método para redondear mi tiempo. https://github.com/travisjeffery/timecop/issues/122#issuecomment-39913650 – lobati

19

Estoy totalmente de acuerdo con las respuestas anteriores sobre el trozo Time.now. Eso dijo que hay otra cosa pasando aquí. Cuando comparas fechas y horas de una base de datos, pierdes parte del tiempo de facción que puede estar en un objeto DateTime de ruby. La mejor manera de comparar la fecha de esa manera en Rspec es:

database_start_date.should be_within(1).of(start_date) 
0

Una solución que me gusta es añadir la siguiente para spec_helper:

class Time 
    def ==(time) 
    self.to_i == time.to_i 
    end 
end 

De esa manera es totalmente transparente incluso en objetos anidados.

+0

Parece una mala idea cambiar el comportamiento de una clase principal solo en sus pruebas. –

+0

De hecho, me encontré con algunos problemas con esto, ya que hay una variedad de clases similares al tiempo que aparecen en Rails. Probablemente comenzaré a integrar Timecop en mis proyectos, aunque sospecho que hacen algo similar. – lobati

0

Agregar .to_datetime a las dos variables forzará a los valores de fecha y hora a ser equivalentes y respetar las zonas horarias y el horario de verano. Para las comparaciones de fechas simples, use .to_date.

Un ejemplo de especificaciones con dos variables: actual_time.to_datetime.should == expected_time.to_datetime

Una mejor especificación con claridad: actual_time.to_datetime.should eq 1.month.from_now.to_datetime

.to_i produce ambigüedad en cuanto a su significado en las especificaciones.

+1 para usar la gema TimeCop en las especificaciones. Solo asegúrate de probar el horario de verano en tus especificaciones si tu aplicación se ve afectada por el horario de verano.

0

Nuestra solución actual es tener un método freeze_time que maneja el redondeo:

def freeze_time(time = Time.zone.now) 
    # round time to get rid of nanosecond discrepancies between ruby time and 
    # postgres time 
    time = time.round 
    Timecop.freeze(time) { yield(time) } 
end 

Y luego se puede utilizar como:

freeze_time do 
    perform_work 
    expect(message.reload.sent_date).to eq(Time.now) 
end 
0

Dependiendo de sus especificaciones, que podría ser capaz de uso de los carriles-nativa travel helpers:

# in spec_helper.rb 
config.include ActiveSupport::Testing::TimeHelpers 

start_date ||= Time.current.change(usecs: 0) 
end_date = start_date + goal_months.months 
travel_to start_date do 
    # Clone here 
end 
expect(clone.start_date).to eq(start_date) 

Sin Time.current.change(usecs: 0) es probable que se queje sobre la diferencia entre las zonas horarias. O entre los microsegundos, ya que el ayudante restablecerá el valor pasado internamente (Timecop tiene un problema similar, así que restaure usecs con él también).

Cuestiones relacionadas