tests run rails how example describe ruby rspec rspec2

ruby - run - Rails-RSpec-Diferencia entre "let" y "let!"



rspec example (6)

He leído lo que dice el manual RSpec sobre la diferencia, pero algunas cosas todavía son confusas. Cualquier otra fuente, incluido "The RSpec Book", solo explica sobre "let" y "The Rails 3 Way" es tan confuso como el manual.

Entiendo que "let" solo se evalúa cuando se invoca, y mantiene el mismo valor dentro de un alcance. Por lo tanto, tiene sentido que en el primer ejemplo del manual pase la primera prueba ya que el "let" se invoca solo una vez, y la segunda prueba pasa ya que se suma al valor de la primera prueba (que se evaluó una vez en la primera prueba) y tiene el valor de 1).

Después de eso, desde "¡deja!" evalúa cuando se define, y de nuevo cuando se invoca, ¿no debería fallar la prueba ya que "count.should eq (1)" debería tener en su lugar "count.should eq (2)"?

Cualquier ayuda sería apreciada.


¡Entendí la diferencia entre let y let! con un ejemplo muy simple. Permítame leer la oración del documento primero, luego mostrar la salida de manos.

Acerca de let doc dice: -

... let es lazy-evaluado : no se evalúa hasta la primera vez que se invoca el método que define.

Entendí la diferencia con el siguiente ejemplo:

$count = 0 describe "let" do let(:count) { $count += 1 } it "returns 1" do expect($count).to eq(1) end end

Vamos a ejecutarlo ahora: -

arup@linux-wzza:~/Ruby> rspec spec/test_spec.rb F Failures: 1) let is not cached across examples Failure/Error: expect($count).to eq(1) expected: 1 got: 0 (compared using ==) # ./spec/test_spec.rb:8:in `block (2 levels) in <top (required)>'' Finished in 0.00138 seconds (files took 0.13618 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/test_spec.rb:7 # let is not cached across examples arup@linux-wzza:~/Ruby>

¿Por qué el ERROR ? Porque, como dijo el doc., Con let , no se evalúa hasta la primera vez que se invoca el método que define. En el ejemplo , no invocamos el count , por lo tanto, $count sigue siendo 0 , no incrementado en 1 .

Ahora viene a la parte, let! . El doctor está diciendo

.... Puedes usar let! forzar la invocación del método antes de cada ejemplo. Significa que incluso si no invocaste el método de ayuda dentro del ejemplo , aún se invocará antes de que se ejecute tu ejemplo.

Vamos a probar esto también:

Aquí está el código modificado

$count = 0 describe "let!" do let!(:count) { $count += 1 } it "returns 1" do expect($count).to eq(1) end end

Vamos a ejecutar este código: -

arup@linux-wzza:~/Ruby> rspec spec/test_spec.rb . Finished in 0.00145 seconds (files took 0.13458 seconds to load) 1 example, 0 failures

Ver, ahora $count devuelve 1 , por lo tanto, la prueba se aprobó. ¡Sucedió cuando solía let! , que se ejecuta antes de la ejecución de ejemplo, aunque no invocamos count dentro de nuestro ejemplo.

¡Así es como lo let y let! difiere el uno del otro.



No se invoca cuando se define, sino antes de cada ejemplo (y luego se memoriza y no se vuelve a invocar con el ejemplo). De esta forma, count tendrá un valor de 1.

De todos modos, si tiene otro ejemplo, se vuelve a invocar el enlace anterior, pasan todas las pruebas siguientes:

$count = 0 describe "let!" do invocation_order = [] let!(:count) do invocation_order << :let! $count += 1 end it "calls the helper method in a before hook" do invocation_order << :example invocation_order.should == [:let!, :example] count.should eq(1) end it "calls the helper method again" do count.should eq(2) end end


Puede leer más sobre esto here , pero básicamente. (:let) se evalúa de forma diferida y nunca se creará una instancia si no se llama, mientras que (:let!) se evalúa con fuerza antes de cada llamada al método.


También pensé que esto era confuso, pero creo que los ejemplos de The Rails 3 Way son buenos.
let es análogo a las variables de instancia en el bloque before mientras que let! se memoriza de inmediato

De The Rails 3 Way

describe BlogPost do let(:blog_post) { BlogPost.create :title => ''Hello'' } let!(:comment) { blog_post.comments.create :text => ''first post'' } describe "#comment" do before do blog_post.comment("finally got a first post") end it "adds the comment" do blog_post.comments.count.should == 2 end end end

"Dado que el bloque de comentarios nunca se habría ejecutado para la primera aserción si utilizó una definición de let, solo se habría agregado un comentario en esta especificación aunque la implementación esté funcionando. Al usar let !, nos aseguramos de que se genere el comentario inicial. y la especificación ahora pasará ".


Y aquí hay una manera de mantener sus especificaciones predecibles.

Deberías usar siempre let . ¡No deberías usar let! a menos que intencionalmente quiera almacenar en caché el valor entre los ejemplos. Esta es la razón por:

describe ''#method'' do # this user persists in the db across all sub contexts let!(:user) { create :user } context ''scenario 1'' do context ''sub scenario'' do # ... # 1000 lines long # ... end context ''sub scenario'' do # you need to test user with a certain trait # and you forgot someone else (or yourself) already has a user created # with `let!` all the way on the top let(:user) { create :user, :trait } it ''fails even though you think it should pass'' do # this might not be the best example but I found this pattern # pretty common in different code bases # And your spec failed, and you scratch your head until you realize # there are more users in the db than you like # and you are just testing against a wrong user expect(User.first.trait).to eq xxx end end end end