tutorial tests test run rails how ruby-on-rails ruby tdd rspec rr

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