インスタンス変数を設定するためにブロックの前に使用する傾向があります。私はそれから私の例の向こう側にそれらの変数を使う。私は最近let()
に遭遇しました。 RSpecの文書によると、
...メモ化されたヘルパーメソッドを定義します。値は、同じ例では複数の呼び出しにまたがってキャッシュされますが、例には含まれません。
これがbeforeブロックでインスタンス変数を使うのとどう違うのですか?また、いつlet()
vs before()
を使うべきですか?
いくつかの理由で、私は常にlet
をインスタンス変数よりも優先します。
nil
に初期化されるため、微妙なバグや誤検知につながる可能性があります。 let
はメソッドを作成するので、スペルを間違えるとNameError
が得られます。仕様のリファクタリングも簡単になります。before(:each)
フックは各例の前に実行されます。これは大したことではありませんが、インスタンス変数の設定に時間がかかる場合は、サイクルが無駄になります。 let
で定義されたメソッドの場合、初期化コードは、例がそれを呼び出す場合にのみ実行されます。@
を追加)。let
で定義し、it
ブロックをNiceとshortにしておくことが好きです。関連リンクはこちらです。 http://www.betterspecs.org/#let
インスタンス変数を使用することとlet()
を使用することの違いは、let()
はlazy-evaluateです。つまり、let()
は、それが定義するメソッドが初めて実行されるまで評価されません。
before
とlet
の違いは、let()
は 'カスケード'スタイルで変数のグループを定義するのに良い方法を提供するということです。こうすることで、コードを単純化することで仕様が少し良くなります。
私の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の前のブロックの取得の利点
letは遅延評価され、その中に副作用メソッドを入れないことに留意することが重要です。そうしなければletから変更することはできません。 )(:each)の前に簡単に。各シナリオの前に評価されるように、---(===)letの代わりにlet!を使用できます。 。
一般的に、let()
はより良い構文であり、至る所に@name
記号を入力する手間を省きます。しかし、警告emptor!let()
には微妙なバグ(または少なくとも頭のスクラッチ)もあります。それを使用してください... let()
の後にputs
を追加して変数が正しいことを確認すると、仕様は通過できますが、puts
がないと仕様は失敗します。
また、let()
はすべての状況でキャッシュされるとは限らないこともわかりました。私は私のブログでそれを書きました: http://technicaldebt.com/?p=1242
たぶんそれは私だけ?
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}
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
"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
私は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
それはスペックを清潔で読みやすくします。