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.
¡También estaba confundido por let
y let!
, así que tomé el código de documentación de here y jugué con él: https://gist.github.com/3489451
¡Espero eso ayude!
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