ruby on rails - tests - ¿Cuál es la mejor práctica cuando se trata de probar “bucles infinitos”?
rspec rails tutorial (10)
¿Qué hay de burlarse del bucle para que se ejecute solo el número de veces que especifique?
Module Object
private
def loop
3.times { yield }
end
end
Por supuesto, te burlas de esto solo en tus especificaciones.
Mi lógica básica es tener un bucle infinito corriendo en algún lugar y probarlo lo mejor posible. La razón para tener un bucle infinito no es importante (bucle principal para juegos, lógica tipo demonio ...) y estoy preguntando más acerca de las mejores prácticas en una situación como esa.
Tomemos este código por ejemplo:
module Blah
extend self
def run
some_initializer_method
loop do
some_other_method
yet_another_method
end
end
end
Quiero probar el método Blah.run
usando Rspec (también uso RR , pero rspec simple sería una respuesta aceptable).
Creo que la mejor manera de hacerlo sería descomponer un poco más, como separar el bucle en otro método o algo así:
module Blah
extend self
def run
some_initializer_method
do_some_looping
end
def do_some_looping
loop do
some_other_method
yet_another_method
end
end
end
... esto nos permite probar la run
y simular el bucle ... pero en algún punto es necesario probar el código dentro del bucle.
Entonces, ¿qué harías en tal situación?
¿Simplemente no está probando esta lógica, lo que significa probar un poco de otro some_other_method
y yet_another_method
some_other_method
yet_another_method
pero no un do_some_looping
?
¿Se ha roto el bucle en algún momento a través de un simulacro?
... ¿algo más?
¿Qué tal si tenemos el cuerpo del bucle en un método separado, como la calculateOneWorldIteration
? De esa manera, puedes hacer el bucle en la prueba según sea necesario. Y no daña la API, es un método bastante natural para tener en la interfaz pública.
:) Tuve esta consulta hace unos meses.
La respuesta corta es que no hay una manera fácil de probar eso. Usted prueba la unidad interna del bucle. Luego lo colocas en un método de bucle y haces una prueba manual de que el bucle funciona hasta que se produce la condición de terminación.
Casi siempre uso una construcción catch / throw para probar bucles infinitos.
El aumento de un error también puede funcionar, pero eso no es ideal, especialmente si el bloqueo de su bucle rescata todos los errores, incluidas las excepciones. Si su bloque no recupera la Excepción (o alguna otra clase de error), puede subclase de Excepción (u otra clase no rescatada) y rescatar su subclase:
Ejemplo de excepción
Preparar
class RspecLoopStop < Exception; end
Prueba
blah.stub!(:some_initializer_method)
blah.should_receive(:some_other_method)
blah.should_receive(:yet_another_method)
# make sure it repeats
blah.should_receive(:some_other_method).and_raise RspecLoopStop
begin
blah.run
rescue RspecLoopStop
# all done
end
Ejemplo de captura / lanzamiento:
blah.stub!(:some_initializer_method)
blah.should_receive(:some_other_method)
blah.should_receive(:yet_another_method)
blah.should_receive(:some_other_method).and_throw :rspec_loop_stop
catch :rspec_loop_stop
blah.run
end
Cuando intenté esto por primera vez, me preocupaba que el uso de should_receive
por segunda vez :some_other_method
"sobrescribiera" el primero, pero este no es el caso. Si quieres verlo por ti mismo, agrega bloques a should_receive
para ver si se llama el número esperado de veces:
blah.should_receive(:some_other_method) { puts ''received some_other_method'' }
La solución más fácil que encontré es producir el bucle una vez y luego regresar. He usado mocha aquí.
require ''spec_helper''
require ''blah''
describe Blah do
it ''loops'' do
Blah.stubs(:some_initializer_method)
Blah.stubs(:some_other_method)
Blah.stubs(:yet_another_method)
Blah.expects(:loop).yields().then().returns()
Blah.run
end
end
Esperamos que el bucle se ejecute realmente y se garantice que saldrá después de una iteración.
Sin embargo, como se indicó anteriormente, es una buena práctica mantener el método de bucle lo más pequeño y estúpido posible.
¡Espero que esto ayude!
Lo que podría ser más práctico es ejecutar el bucle en un hilo separado, afirmar que todo funciona correctamente y luego terminar el hilo cuando ya no sea necesario.
thread = Thread.new do
Blah.run
end
assert_equal 0, Blah.foo
thread.kill
No se puede probar que algo que se ejecuta para siempre.
Cuando te enfrentas a una sección de código que es difícil (o imposible) de probar, debes:
- Refactorizar para aislar la parte difícil de probar del código. Haga las partes no probables pequeñas y triviales. Comenta para asegurarte de que no se expandan más tarde para que no sean triviales.
- Haga una prueba unitaria de las otras partes, que ahora están separadas de la sección difícil de probar
- La parte difícil de probar estaría cubierta por una prueba de integración o aceptación
Si el bucle principal en tu juego solo gira una vez, esto será inmediatamente obvio cuando lo ejecutes.
Nuestra solución para probar un bucle que solo sale de las señales fue detener el método de condición de salida para que devuelva false la primera vez, pero la segunda vez, asegurando que el bucle solo se ejecute una vez.
Clase con bucle infinito:
class Scheduling::Daemon
def run
loop do
if daemon_received_stop_signal?
break
end
# do stuff
end
end
end
Especificaciones que prueban el comportamiento del bucle:
describe Scheduling::Daemon do
describe "#run" do
before do
Scheduling::Daemon.should_receive(:daemon_received_stop_signal?).
and_return(false, true) # execute loop once then exit
end
it "does stuff" do
Scheduling::Daemon.run
# assert stuff was done
end
end
end
Sé que esto es un poco viejo, pero también puedes usar el método de rendimientos para simular un bloque y pasar una única iteración a un método de bucle. Esto debería permitirle probar los métodos a los que está llamando dentro de su bucle sin ponerlo realmente en un bucle infinito.
require ''test/unit''
require ''mocha''
class Something
def test_method
puts "test_method"
loop do
puts String.new("frederick")
end
end
end
class LoopTest < Test::Unit::TestCase
def test_loop_yields
something = Something.new
something.expects(:loop).yields.with() do
String.expects(:new).returns("samantha")
end
something.test_method
end
end
# Started
# test_method
# samantha
# .
# Finished in 0.005 seconds.
#
# 1 tests, 2 assertions, 0 failures, 0 errors
en rspec 3.3, agregue esta línea
allow(subject).to receive(:loop).and_yield
a su gancho anterior se cederá fácilmente al bloque sin ningún bucle