2012-04-16 16 views
26

He leído lo que dice RSpec manual 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.Rails - RSpec - ¡Diferencia entre "let" y "let!"

Entiendo que "let" solo se evalúa cuando se invoca, y mantiene el mismo valor dentro de un ámbito. Por lo tanto, tiene sentido que en el primer ejemplo en el manual pase la primera prueba ya que el "let" se invoca solo una vez, y la segunda pasa ya que agrega el 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 "let!" 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.

Respuesta

8

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 manera, el recuento tendrá un valor de 1.

De todos modos, si usted tiene otro ejemplo, el gancho antes de que se invoca de nuevo - todas las siguientes pruebas Pass:

$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 
+0

De acuerdo, por lo que la primera especificación tiene sentido. Seguramente, sin embargo, la segunda especificación se evaluaría antes del ejemplo, (haciéndolo igual a 1) y luego otra vez cuando se llama al "conteo.debería" (haciéndolo igual a 2). –

+3

No, ya se invoca y se memoriza, por lo que el recuento de $ no vuelve a aumentar. De todos modos, si tiene otro ejemplo, se vuelve a invocar el enlace anterior. Ver mi respuesta editada, agregué un código para aclararme. – dhoelzgen

+0

Eso significa que si usa "let" y una especificación (un "it") no hace "count".debería "(o similar) entonces el incremento no tendrá lugar? Si ese es el caso, entonces" let "no debería considerarse un" antes ", ya que antes de forma predeterminada implica la funcionalidad de" let! ". O ¿Me estoy perdiendo algo de nuevo? –

21

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

+0

Gracias Justin. Ese fue el "manual" al que me vinculé que no entendía. Mi pregunta se centró más en por qué la "2da especificación" en ese enlace pasó con "count.should eq (1)" en lugar de "count.should eq (2)" como esperaba. Si se evalúa antes de la llamada al método, y de nuevo cuando se llama al "count.should", ¿el segundo ejemplo no debería ser igual a 2 en lugar de 1? –

+1

halle-flippin-lujah, finalmente una explicación que no se aburre sobre la memorización o si eso es así. Simple y al grano, como debería ser todo. –

2

también estaba confundido por let y let!, así que tomé el código de la documentación de here y jugado con él: https://gist.github.com/3489451

espero que ayude!

+0

Gracias @JonathanLinErnSheong. Aclaró algunas cosas –

+0

¡Qué bueno es saberlo! :) –

4

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 memoized inmediatamente

de los rieles de 3 vías

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 

"Desde el bloque de comentario nunca habría sido ejecutado por primera afirmación de si se ha utilizado una definición let, sólo un comentario tendría habido agregado en esta especificación aunque la implementación pueda estar funcionando. Al usar let! nos aseguramos de que se cree el comentario inicial y ahora se pase la especificación ".

35

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.

About let doc dice: -

... let es -perezoso evaluado: no se evalúa hasta la primera vez el método que se invoca 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 

que le permite correr ahora: -

[email protected]:~/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 
[email protected]:~/Ruby> 

Por qué la ERROR? Porque, como dice el documento, con let, , no se evalúa hasta la primera vez que se invoca el método que define. En el ejemplo, no hemos invocado el count, por lo tanto $count sigue siendo 0, no se ha incrementado en 1.

Ahora viene a la parte let!. El doc dice

.... Puedes usar let! forzar la invocación del método antes de cada ejemplo. Esto significa que incluso si no invocó el helper método dentro del ejemplo, aún se invocará antes de ejecutar su ejemplo.

Permite probar esto también: -

Este es el código modificado

$count = 0 
describe "let" do 
    let!(:count) { $count += 1 } 

    it "returns 1" do 
    expect($count).to eq(1) 
    end 
end 

Permite ejecutar este código: -

[email protected]:~/Ruby> rspec spec/test_spec.rb 
. 

Finished in 0.00145 seconds (files took 0.13458 seconds to load) 
1 example, 0 failures 

Sede, ahora $count vuelve 1, por lo tanto la prueba ha pasado. Sucedió cuando usé let!, que se ejecuta antes de la ejecución de ejemplo, aunque no invocamos count dentro de nuestro ejemplo.

Así es como let y let! se diferencian entre sí.

+2

simple y claro. ¡Gracias! –

+2

Muchas gracias! He estado buscando en Google esto por dos horas y finalmente bajo ¡estar! –

+0

Recuerda, nunca tengas que dejar un bloque dentro de una antes del bloque, esto es lo que dejas! está hecho para. Para obtener más información, consulte esta página: https://kolosek.com/rspec-let-vs-before –

1

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

Casi siempre debe usar let. No debe usar let! a menos que intencionalmente quiera almacenar el valor en caché en todos los ejemplos. Esta es la razón por la cual:

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 
Cuestiones relacionadas