web-dev-qa-db-ja.com

Rails-RSpec-「let」と「let!」の違い

私は RSpecマニュアル が違いについて述べていることを読みましたが、いくつかはまだ混乱しています。 「The RSpec Book」を含むその他すべてのソースは「let」についてのみ説明しており、「The Rails 3 Way」はマニュアルと同じくらい混乱しています。

「let」は呼び出されたときにのみ評価され、スコープ内で同じ値を保持することを理解しています。したがって、 manual の最初の例では、「let」が1回だけ呼び出されると最初のテストに合格し、2番目のテストは最初のテストの値(最初のテストで一度評価され、値は1)です。

続いて「let!」から「count.should eq(1)」が「count.should eq(2)」でなければならないので、定義時に評価され、呼び出されたときに再びテストが失敗しないか?

任意の助けいただければ幸いです。

40
Theo Scholiadis

定義時に呼び出されるのではなく、各例の前に呼び出されます(メモ化され、例によって再度呼び出されることはありません)。これにより、countの値は1になります。

とにかく、別の例がある場合は、beforeフックが再度呼び出されます。以下のすべてのテストに合格します。

$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
14
dhoelzgen

非常に単純な例でletlet!の違いを理解しました。最初にdoc文を読んでから、出力を実際に見てみましょう。

letについて docによると:-

... let is lazy-evaluated:定義されているメソッドが初めて呼び出されるまで評価されません。

私は以下の例との違いを理解しました:-

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

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

今すぐ実行してみましょう:-

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>

[〜#〜] error [〜#〜]なのはなぜですか?なぜなら、docが言ったように、letではそれが定義するメソッドが最初に呼び出されるまで評価されないからです。- examplecountを呼び出さなかったため、$count0のままであり、1によってインクリメントされていません。

パートlet!に移動します。ドクは言っています

.... letを使用できます!各例の前にメソッドの呼び出しを強制します。 help]メソッドをexample内で呼び出さなくても、例が実行される前に呼び出されます。

これもテストしてみましょう:-

これが変更されたコードです

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

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

このコードを実行してみましょう:-

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

さて、今度は$count1を返すため、テストに合格しました。これは、サンプル内でcountを呼び出さなかったものの、サンプルの実行前に実行されるlet!を使用したときに発生しました。

これがletlet!の違いです。

48
Arup Rakshit

あなたはこれについてもっと読むことができます ここ 、しかし基本的に。 (:let)は遅延評価され、呼び出さない限りインスタンス化されませんが、(:let!)は、各メソッド呼び出しの前に強制的に評価されます。

27
Justin Herrick

私もこれは混乱を招くと思いましたが、Rails 3 Wayの例が良いと思います。
letはbeforeブロックのインスタンス変数に似ていますが、let!すぐにメモされる

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

「let定義を使用した場合、最初のアサーションに対してコメントブロックは実行されなかったため、実装が機能している場合でも、この仕様にはコメントが1つだけ追加されます。letを使用することにより、最初のコメントが作成されるようにします。そして、スペックは合格するでしょう。」

5
cluv

letlet!にも混乱していたので、ドキュメントコードを here から取得して、次のように操作しました: https://Gist.github。 com/3489451

それが役に立てば幸い!

2
Jonathan Lin

そして、これがあなたのスペックを予測可能に保つ方法です。

ほとんど常にletを使用する必要があります。意図的に例全体の値をキャッシュしたい場合を除き、let!を使用しないでください。これが理由です:

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
1
Edmund Lee