web-dev-qa-db-ja.com

RSpec let()を使用する場合

インスタンス変数を設定するためにブロックの前に使用する傾向があります。私はそれから私の例の向こう側にそれらの変数を使う。私は最近let()に遭遇しました。 RSpecの文書によると、

...メモ化されたヘルパーメソッドを定義します。値は、同じ例では複数の呼び出しにまたがってキャッシュされますが、例には含まれません。

これがbeforeブロックでインスタンス変数を使うのとどう違うのですか?また、いつlet() vs before()を使うべきですか?

435
sent-hil

いくつかの理由で、私は常にletをインスタンス変数よりも優先します。

  • 参照されると、インスタンス変数は存在するようになります。つまり、インスタンス変数のスペルを太ったままにすると、新しい変数が作成されてnilに初期化されるため、微妙なバグや誤検知につながる可能性があります。 letはメソッドを作成するので、スペルを間違えるとNameErrorが得られます。仕様のリファクタリングも簡単になります。
  • 例でフックで定義されているインスタンス変数が使用されていない場合でも、before(:each)フックは各例の前に実行されます。これは大したことではありませんが、インスタンス変数の設定に時間がかかる場合は、サイクルが無駄になります。 letで定義されたメソッドの場合、初期化コードは、例がそれを呼び出す場合にのみ実行されます。
  • 例の中の参照構文を変更せずに、例の中のローカル変数から直接letにリファクタリングできます。インスタンス変数をリファクタリングする場合は、例の中でオブジェクトを参照する方法を変更する必要があります(例:@を追加)。
  • これは少し主観的ですが、Mike Lewisが指摘したように、仕様が読みやすくなると思います。私はすべての依存オブジェクトをletで定義し、itブロックをNiceとshortにしておくことが好きです。

関連リンクはこちらです。 http://www.betterspecs.org/#let

592
Myron Marston

インスタンス変数を使用することとlet()を使用することの違いは、let()lazy-evaluateです。つまり、let()は、それが定義するメソッドが初めて実行されるまで評価されません。

beforeletの違いは、let()は 'カスケード'スタイルで変数のグループを定義するのに良い方法を提供するということです。こうすることで、コードを単純化することで仕様が少し良くなります。

79
Mike Lewis

私のrspecテストでインスタンス変数のすべての使用法をlet()を使用するように完全に置き換えました。私は小さなRspecクラスを教えるためにそれを使った友人のための簡単な例を書きました: http://Ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html

ここで他のいくつかの答えが言うように、let()は遅延評価されるので、ロードを必要とするものだけをロードします。それは仕様を乾燥させ、それをより読みやすくします。私は実際にはRspec let()コードを私のコントローラで使うためにinherited_resource gemのスタイルで移植しました。 http://Ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html

遅延評価に加えて、もう1つの利点は、ActiveSupport :: Concern、およびすべてロードイン仕様/サポート/動作と組み合わせて、ご使用のアプリケーションに固有の独自の仕様ミニDSLを作成できることです。私はRackとRESTfulリソースに対してテストするためのものを書きました。

私が使う戦略はFactory-everythingです(Machinist + Forgery/Faker)。ただし、これをbefore(:each)ブロックと組み合わせて使用​​することで、一連のサンプルグループ全体のファクトリをプリロードし、仕様の実行速度を速めることができます。 http://makandra.com/notes/770) rspec-sの前のブロックの取得の利点

17
Ho-Sheng Hsiao

letは遅延評価され、その中に副作用メソッドを入れないことに留意することが重要です。そうしなければletから変更することはできません。 )(:each)の前に簡単に。各シナリオの前に評価されるように、---(===)letの代わりにlet!を使用できます。 。

13
pisaruk

一般的に、let()はより良い構文であり、至る所に@name記号を入力する手間を省きます。しかし、警告emptor!let()には微妙なバグ(または少なくとも頭のスクラッチ)もあります。それを使用してください... let()の後にputsを追加して変数が正しいことを確認すると、仕様は通過できますが、putsがないと仕様は失敗します。

また、let()はすべての状況でキャッシュされるとは限らないこともわかりました。私は私のブログでそれを書きました: http://technicaldebt.com/?p=1242

たぶんそれは私だけ?

8
Jon Kern

letは本質的にProcとして機能します。またそのキャッシュ。

ひとつの変更をすぐに見つけました...変更を評価しているSpecブロックで。

let(:object) {FactoryGirl.create :object}

expect {
  post :destroy, id: review.id
}.to change(Object, :count).by(-1)

Expectブロックの外側でletを呼び出すようにしてください。すなわち、あなたはあなたのletブロックでFactoryGirl.createを呼んでいます。私は通常、オブジェクトが永続化されていることを確認することによってこれを行います。

object.persisted?.should eq true

そうでない場合、letブロックが最初に呼び出されたときに、データベース内の変更が実際には遅延インスタンス化によって発生します。

更新

メモを追加するだけです。慎重にプレーしてください code golf またはこの場合はrspec golfでこの答えを入力してください。

この場合は、オブジェクトが応答するメソッドを呼び出すだけで済みます。それで私はその真実としてオブジェクトの_.persisted?_メソッドを呼び出します。私がやろうとしているのは、オブジェクトをインスタンス化することだけです。あなたは空と呼ぶことができますか?またはゼロ?も。要点はテストではなく、それを呼び出すことによってオブジェクトを生命に結びつけることです。

だからあなたはリファクタリングすることはできません

object.persisted?.should eq true

することが

object.should be_persisted 

オブジェクトはインスタンス化されていないので...怠惰です。 :)

アップデート2

インスタントオブジェクト作成のために let!構文 を利用することで、この問題を完全に回避することができます。それは非拍手letの怠惰の目的の多くを無効にしますが注意してください。

また、場合によっては、letを追加のオプションとして使用するのではなく、実際には subject syntax を利用することをお勧めします。

subject(:object) {FactoryGirl.create :object}
5
engineerDave

Joseph氏へのメモ - あなたがbefore(:all)でデータベースオブジェクトを作成しているならば、それらはトランザクションで捕獲されないでしょう、そしてあなたはあなたのテストデータベースに巧妙に残す可能性がはるかに高いです。代わりにbefore(:each)を使用してください。

Letとその遅延評価を使用するもう1つの理由は、次の非常に人為的な例のように、複雑なオブジェクトを取得してコンテキスト内でletをオーバーライドすることによって個々の部分をテストできるようにすることです。

context "foo" do
  let(:params) do
     { :foo => foo,  :bar => "bar" }
  end
  let(:foo) { "foo" }
  it "is set to foo" do
    params[:foo].should eq("foo")
  end
  context "when foo is bar" do
    let(:foo) { "bar" }
    # NOTE we didn't have to redefine params entirely!
    it "is set to bar" do
      params[:foo].should eq("bar")
    end
  end
end
2
dotdotdotPaul

"before"はデフォルトでbefore(:each)を意味します。 Rspec Book、著作権2010、228ページを参照してください。

before(scope = :each, options={}, &block)

"it"ブロックにデータを作成するためにletメソッドを呼び出す必要なしに、before(:each)を使用して各サンプルグループのデータをシードします。この場合、 "it"ブロック内のコードが少なくなります。

いくつかの例にいくつかのデータが欲しいが他の例には欲しくないなら、私はletを使います。

「it」ブロックを乾燥させるには、beforeとletの両方が最適です。

混乱を避けるために、 "let"はbefore(:all)と同じではありません。 "Let"はそれぞれの例に対してそのメソッドと値を再評価します( "it")が、同じ例では複数の呼び出しにわたってその値をキャッシュします。あなたはここでそれについてもっと読むことができます: https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let

1
konyak

私はletを使用して、コンテキストを使用して私のAPI仕様でHTTP 404応答をテストします。

リソースを作成するために、私はlet!を使います。しかし、リソース識別子を保存するために、私はletを使います。それがどのように見えるか見てみましょう:

let!(:country)   { create(:country) }
let(:country_id) { country.id }
before           { get "api/countries/#{country_id}" }

it 'responds with HTTP 200' { should respond_with(200) }

context 'when the country does not exist' do
  let(:country_id) { -1 }
  it 'responds with HTTP 404' { should respond_with(404) }
end

それはスペックを清潔で読みやすくします。

0
vnbrs