Cuándo usar RSpec let ()?

Tiendo a usar antes de bloques para establecer variables de instancia. Luego uso esas variables en mis ejemplos. Recientemente encontré let() . De acuerdo con los documentos de RSpec, se usa para

… para definir un método de ayuda memorado. El valor se almacenará en caché en varias llamadas en el mismo ejemplo, pero no en todos los ejemplos.

¿En qué se diferencia esto de usar variables de instancia antes de los bloques? Y también, ¿cuándo deberías usar let() vs. before() ?

Siempre prefiero let una variable de instancia por un par de razones:

  • Las variables de instancia surgen cuando se hace referencia a ellas. Esto significa que si se pasa de moda la ortografía de la variable de instancia, se creará una nueva y se inicializará a nil , lo que puede dar lugar a errores sutiles y falsos positivos. Como let crea un método, obtendrás un NameError cuando lo NameError mal, lo cual es preferible. También facilita la refactorización de las especificaciones.
  • Un anzuelo before(:each) se ejecutará antes de cada ejemplo, incluso si el ejemplo no utiliza ninguna de las variables de instancia definidas en el gancho. Esto no suele ser un gran problema, pero si la configuración de la variable de instancia lleva mucho tiempo, estás perdiendo ciclos. Para el método definido por let , el código de inicialización solo se ejecuta si el ejemplo lo llama.
  • Puede refactorizar desde una variable local en un ejemplo directamente en let sin cambiar la syntax de referencia en el ejemplo. Si se refactoriza a una variable de instancia, debe cambiar la forma en que hace referencia al objeto en el ejemplo (por ejemplo, agregue un @ ).
  • Esto es un poco subjetivo, pero como Mike Lewis señaló, creo que hace que las especificaciones sean más fáciles de leer. Me gusta la organización de definir todos mis objetos dependientes con let y mantener mi bloque bueno y corto.

La diferencia entre el uso de variables de instancias y let() es que let() se evalúa de forma diferida . Esto significa que let() no se evalúa hasta que el método que define se ejecuta por primera vez.

La diferencia entre before y let es que let() te da una forma agradable de definir un grupo de variables en un estilo ‘cascada’. Al hacer esto, la especificación se ve un poco mejor al simplificar el código.

He reemplazado completamente todos los usos de variables de instancia en mis pruebas de rspec para usar let (). He escrito un ejemplo rápido para un amigo que lo usó para enseñar una pequeña clase de Rspec: http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html

Como algunas de las otras respuestas aquí dicen, let () se evalúa como flojo, por lo que solo cargará las que requieren carga. Seca la especificación y la hace más legible. De hecho, he portado el código Rspec let () para usar en mis controladores, al estilo de inherited_resource gem. http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html

Junto con la evaluación diferida, la otra ventaja es que, combinada con ActiveSupport :: Concern, y la especificación / soporte / comportamiento load-everything-in, puede crear su propia mini-DSL específica para su aplicación. He escrito algunos para probar contra Rack y recursos RESTful.

La estrategia que uso es Factory-everything (a través de Machinist + Forgery / Faker). Sin embargo, es posible usarlo en combinación con bloques before (: each) para precargar fábricas para un conjunto completo de grupos de ejemplo, permitiendo que las especificaciones se ejecuten más rápidamente: http://makandra.com/notes/770-taking-advantage -de-rspec-s-let-in-before-blocks

Es importante tener en cuenta que let es evaluado de forma diferida y no pone métodos de efectos secundarios en él; de lo contrario, no sería capaz de cambiar de let a before (: each) fácilmente. Puedes usar let! en lugar de dejar para que se evalúe antes de cada escenario.

En general, let() es una syntax más agradable y te ahorra escribir símbolos @name todos lados. Pero, ¡ caveat emptor! He encontrado que let() también introduce errores sutiles (o al menos rasguños en la cabeza) porque la variable realmente no existe hasta que intentas usarla … Dile a la señal del cuento: si se agrega una puts después de let() para ver eso la variable es correcta permite que una especificación pase, pero sin las puts la especificación falla; has encontrado esta sutileza.

¡También encontré que let() no parece almacenar en caché en todas las circunstancias! Lo escribí en mi blog: http://technicaldebt.com/?p=1242

¿Tal vez sea sólo yo?

let es funcional ya que es esencialmente un Proc. También está en caché.

Uno gotcha encontré enseguida con let … En un bloque Spec que está evaluando un cambio.

 let(:object) {FactoryGirl.create :object} expect { post :destroy, id: review.id }.to change(Object, :count).by(-1) 

Deberá asegurarse de llamar a let fuera de su bloque de espera. es decir, estás llamando a FactoryGirl.create en tu bloque de alquiler. Normalmente hago esto verificando que el objeto persista.

 object.persisted?.should eq true 

De lo contrario, cuando se llame al bloque let la primera vez, se producirá un cambio en la base de datos debido a la instanciación lenta.

Actualizar

Solo agregando una nota. Tenga cuidado jugando golf code o en este caso rspec golf con esta respuesta.

En este caso, solo tengo que llamar a algún método al que responde el objeto. Entonces invoco el _.persisted? _ método sobre el objeto como su verdad. Todo lo que bash hacer es crear una instancia del objeto. ¿Podrías llamar vacío? o nulo? también. El punto no es la prueba, sino traer el objeto de la vida llamándolo.

Entonces no puedes refactorizar

 object.persisted?.should eq true 

ser

 object.should be_persisted 

como el objeto no ha sido instanciado … es flojo. 🙂

Actualización 2

aprovechar el let! syntax para la creación de objetos instantáneos, lo que debería evitar este problema por completo. Aunque tenga en cuenta que va a derrotar a muchos de los fines de la pereza de la deja sin golpear.

También, en algunos casos, es posible que desee aprovechar la syntax del tema en lugar de dejarlo, ya que puede brindarle opciones adicionales.

 subject(:object) {FactoryGirl.create :object} 

Nota para Joseph: si está creando objetos de base de datos en un before(:all) , no se capturarán en una transacción y es mucho más probable que deje un fragmento en su base de datos de prueba. Usar before(:each) lugar.

La otra razón para usar let y su evaluación perezosa es para que pueda tomar un objeto complicado y probar piezas individuales anulando permite en contextos, como en este ejemplo muy artificial:

 context "foo" do let(:params) do { :foo => foo, :bar => "bar" } end let(:foo) { "foo" } it "is set to foo" do params[:foo].should eq("foo") end context "when foo is bar" do let(:foo) { "bar" } # NOTE we didn't have to redefine params entirely! it "is set to bar" do params[:foo].should eq("bar") end end end 

“antes” por defecto implica before(:each) . Ref The Rspec Book, copyright 2010, página 228.

 before(scope = :each, options={}, &block) 

Uso before(:each) para generar algunos datos para cada grupo de ejemplo sin tener que llamar al método let para crear los datos en el bloque “it”. Menos código en el bloque “it” en este caso.

Uso let si quiero algunos datos en algunos ejemplos pero no en otros.

Tanto antes como después son geniales para SECAR los bloques “it”.

Para evitar confusiones, “dejar” no es lo mismo que before(:all) . “Let” vuelve a evaluar su método y valor para cada ejemplo (“it”), pero guarda el valor en caché en varias llamadas en el mismo ejemplo. Puede leer más sobre esto aquí: https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let

Utilizo let para probar mis respuestas HTTP 404 en mis especificaciones de API usando contextos.

Para crear el recurso, ¡uso let! . Pero para almacenar el identificador de recursos, uso let . Observe cómo se ve:

 let!(:country) { create(:country) } let(:country_id) { country.id } before { get "api/countries/#{country_id}" } it 'responds with HTTP 200' { should respond_with(200) } context 'when the country does not exist' do let(:country_id) { -1 } it 'responds with HTTP 404' { should respond_with(404) } end 

Eso mantiene las especificaciones limpias y legibles.

    Intereting Posts