J'ai lu ce que le manuel RSpec dit sur la différence, mais certaines choses sont toujours confuses. Toutes les autres sources, y compris "The RSpec Book" n'expliquent que "let", et "The Rails 3 Way" est tout aussi déroutant que le manuel.
Je comprends que "let" n'est évalué que lorsqu'il est invoqué et conserve la même valeur dans une portée. Il est donc logique que dans le premier exemple du manuel le premier test réussit car le "let" n'est invoqué qu'une seule fois, et le second test réussit car il ajoute à la valeur du premier test (qui a été évalué une fois lors du premier test et a la valeur 1).
Suite à cela, puisque "laissez!" évalue une fois défini, et de nouveau lorsqu'il est invoqué, le test ne devrait-il pas échouer car "count.should eq (1)" aurait dû plutôt être "count.should eq (2)"?
Toute aide serait appréciée.
Elle n'est pas invoquée lorsqu'elle est définie, mais plutôt avant chaque exemple (puis elle est mémorisée et n'est plus invoquée par l'exemple). De cette façon, count aura une valeur de 1.
Quoi qu'il en soit, si vous avez un autre exemple, le hook avant est à nouveau invoqué - tous les tests suivants réussissent:
$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
J'ai compris la différence entre let
et let!
avec un exemple très simple. Permettez-moi de lire la phrase doc en premier, puis de montrer la sortie.
À propos de let doc dit: -
...
let
is paresseux: il n'est évalué que la première fois que la méthode qu'il définit est invoquée.
J'ai compris la différence avec l'exemple ci-dessous: -
$count = 0
describe "let" do
let(:count) { $count += 1 }
it "returns 1" do
expect($count).to eq(1)
end
end
Permet de l'exécuter maintenant: -
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>
Pourquoi l'erreur [~ # ~] [~ # ~]? Parce que, comme le dit doc, avec let
, il n'est évalué que la première fois que la méthode qu'il définit est invoquée. Dans le = exemple, nous n'avons pas appelé le count
, donc $count
est toujours 0
, non incrémenté de 1
.
Venons-en maintenant à la partie let!
. Le doc dit
.... Vous pouvez utiliser let! pour forcer l'invocation de la méthode avant chaque exemple. Cela signifie que même si vous n'avez pas invoqué la méthode helper à l'intérieur de exemple, elle sera toujours invoquée avant l'exécution de votre exemple.
Testons cela aussi: -
Voici le code modifié
$count = 0
describe "let!" do
let!(:count) { $count += 1 }
it "returns 1" do
expect($count).to eq(1)
end
end
Permet d'exécuter ce code: -
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
Voyez, maintenant $count
Retour 1
, ainsi le test a été réussi. C'est arrivé quand j'ai utilisé let!
, qui s'exécutait avant l'exemple, bien que nous n'ayons pas appelé count
dans notre exemple.
Voici comment let
et let!
diffère les uns des autres.
Vous pouvez en savoir plus à ce sujet ici , mais en gros. (:let)
est évalué paresseusement et ne sera jamais instancié si vous ne l'appelez pas, tandis que (:let!)
est évalué avec force avant chaque appel de méthode.
Je pensais aussi que c'était déroutant, mais je pense que les exemples de The Rails 3 Way sont bons.
let est analogue aux variables d'instance dans le bloc before alors que let! est mémorisé immédiatement
Depuis le 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
"Puisque le bloc de commentaire n'aurait jamais été exécuté pour la première assertion si vous avez utilisé une définition let, un seul commentaire aurait été ajouté dans cette spécification même si l'implémentation peut fonctionner. En utilisant let! Nous nous assurons que le commentaire initial est créé et la spécification va maintenant passer. "
J'ai également été confondu par let
et let!
, j'ai donc pris le code de documentation de ici et joué avec: https://Gist.github.com/3489451
J'espère que ça aide!
Et voici un moyen de garder vos spécifications prévisibles.
Vous devriez presque toujours utiliser let
. Vous ne devez pas utiliser let!
sauf si vous souhaitez intentionnellement mettre en cache la valeur dans les exemples. C'est pourquoi:
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