beforeブロックを使用してインスタンス変数を設定する傾向があります。次に、これらの変数を私の例全体で使用します。最近出会いましたlet()
。RSpecのドキュメントによると、それは
...メモ化されたヘルパーメソッドを定義します。値は、同じ例の複数の呼び出しにわたってキャッシュされますが、例全体ではキャッシュされません。
これは、beforeブロックでインスタンス変数を使用する場合とどう違うのですか?また、いつlet()
vs を使用する必要がありますbefore()
か?
beforeブロックを使用してインスタンス変数を設定する傾向があります。次に、これらの変数を私の例全体で使用します。最近出会いましたlet()
。RSpecのドキュメントによると、それは
...メモ化されたヘルパーメソッドを定義します。値は、同じ例の複数の呼び出しにわたってキャッシュされますが、例全体ではキャッシュされません。
これは、beforeブロックでインスタンス変数を使用する場合とどう違うのですか?また、いつlet()
vs を使用する必要がありますbefore()
か?
回答:
私は常にlet
いくつかの理由でインスタンス変数を好む:
nil
、微妙なバグや誤検知につながる可能性があります。let
メソッドを作成するので、NameError
スペルを間違えるとを取得します。スペックのリファクタリングも簡単になります。before(:each)
フックは例がフックで定義されたインスタンス変数のいずれかを使用しない場合でも、それぞれの例の前に実行されます。これは通常大したことではありませんが、インスタンス変数の設定に長い時間がかかる場合は、サイクルを浪費しています。によって定義されたメソッドのlet
場合、初期化コードは、例がそれを呼び出した場合にのみ実行されます。@
)。let
、it
ブロックを簡潔かつ簡潔に保つ構成が好きです。関連リンクはここにあります:http : //www.betterspecs.org/#let
let
すべての依存オブジェクトを定義し、before(:each)
必要な構成または例で必要なモック/スタブをセットアップするために使用すると便利です。私はこれをすべて含む1つの大きなbeforeフックよりもこれを好みます。また、let(:foo) { Foo.new }
ノイズが少なくなります(要点はさらに高くなります)before(:each) { @foo = Foo.new }
。使用方法の例を次に示します。github.com
NoMethodError
は警告を受け取るよりもを受け取ることを好みますが、YMMVです。
foo = Foo.new(...)
、foo
後の行にユーザーがいます。その後、同じ例のグループで新しい例を記述しFoo
ますが、これも同じ方法でインスタンス化する必要があります。この時点で、重複を排除するためにリファクタリングする必要があります。foo = Foo.new(...)
例から行を削除してlet(:foo) { Foo.new(...) }
、例の使用方法を変更しないw / oで置き換えることができますfoo
。しかし、あなたがにリファクタリングあればbefore { @foo = Foo.new(...) }
、あなたもからの例の参照を更新する必要があるfoo
のを@foo
。
インスタンス変数を使用したとの違いlet()
それでlet()
あるレイジーに評価。これは、let()
それが定義するメソッドが初めて実行されるまで評価されないことを意味します。
との違いはbefore
、変数のグループを「カスケード」スタイルで定義する優れた方法let
をlet()
提供することです。これを行うことにより、コードを簡略化することで、スペックが少し良くなります。
let
毎回何かを評価する必要があるなら、それを使うべきではないということですか?たとえば、親モデルでいくつかの動作がトリガーされる前に、データベースに子モデルが存在する必要があります。親モデルの動作をテストしているため、テストでその子モデルを参照しているとは限りません。現在はlet!
代わりにメソッドを使用していますが、その設定を入れる方がより明示的でしょうbefore(:each)
か?
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、およびload-everything-in spec / support /動作と組み合わせて、アプリケーションに固有の独自の仕様mini-DSLを作成できることです。RackおよびRESTfulのリソースに対してテストするためのものを作成しました。
私が使用する戦略は、ファクトリーエブリシング(Machinist + Forgery / Faker経由)です。ただし、before(:each)ブロックと組み合わせて使用して、サンプルグループのセット全体のファクトリをプリロードし、仕様をより高速に実行することができます:http : //makandra.com/notes/770-taking-advantage -of-rspec-s-let-in-before-blocks
# spec/friendship_spec.rb
と# spec/comment_spec.rb
例について、彼らはそれを読みにくくしていると思いませんか?私はusers
どこから来たのかわからないので、深く掘り下げる必要があります。
let()
過去数日間使用しており、個人的にはMyronが述べた最初の利点を除いて、違いは見られません。そして、私は手放すことについてはあまり確信がありません。おそらく私は怠惰であり、さらに別のファイルを開かなくてもコードを前もって見るのが好きだからです。コメントしてくれてありがとう。
一般に、let()
はより優れた構文であり、@name
記号を入力する手間が省けます。しかし、注意を払う必要があります!私を発見したlet()
追加場合:あなたはそれを使用しようとするまで、変数が実際に存在していないので...物語記号を伝えるにも微妙なバグ(あるいは少なくとも頭掻く)を導入puts
した後、let()
変数が正しいことを確認するためにはスペックを可能に合格しputs
ますが、仕様がなければ失敗します-この微妙な点を見つけました。
またlet()
、すべての状況でキャッシュされないようです。私はブログにそれを書きました:http : //technicaldebt.com/?p=1242
多分それは私だけですか?
let
常に1つの例の期間中の値を記憶します。複数の例の値をメモすることはありません。before(:all)
対照的に、では、初期化された変数を複数の例で再利用できます。
let!
が目的です。 relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/...
letは本質的にProcとして機能します。また、そのキャッシュ。
letですぐに見つけた1つの問題...変更を評価しているSpecブロックで。
let(:object) {FactoryGirl.create :object}
expect {
post :destroy, id: review.id
}.to change(Object, :count).by(-1)
あなたはlet
あなたのexpectブロックの外で呼び出すことを確認する必要があります。つまりFactoryGirl.create
、letブロックで呼び出しています。私は通常、オブジェクトが永続化されていることを確認することでこれを行います。
object.persisted?.should eq true
そうしないと、let
ブロックが初めて呼び出されたときに、遅延インスタンス化のためにデータベースの変更が実際に発生します。
更新
メモを追加するだけです。この回答では、コードゴルフ、またはこの場合はrspecゴルフのプレーに注意してください。
この場合、オブジェクトが応答するメソッドを呼び出すだけです。したがって_.persisted?
、オブジェクトの_メソッドをその真実として呼び出します。私がやろうとしているのは、オブジェクトをインスタンス化することだけです。あなたは空と呼ぶことができますか?またはnil?あまりにも。ポイントはテストではありませんが、それを呼び出すことによってオブジェクトに生命をもたらします。
だからあなたはリファクタリングできません
object.persisted?.should eq true
することが
object.should be_persisted
オブジェクトがインスタンス化されていないので...その遅延。:)
アップデート2
レッツを活用!この問題を完全に回避する必要がある、インスタントオブジェクト作成の構文。ただし、非バンレットレットの遅延の目的の多くは無効になります。
また、一部のインスタンスでは、追加のオプションが提供される場合があるため、letではなく実際に対象の構文を活用したい場合があります。
subject(:object) {FactoryGirl.create :object}
ジョセフへの注意-データベースオブジェクトを作成している場合、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(:each)
。Ref The Rspec Book、copyright 2010、page 228。
before(scope = :each, options={}, &block)
「it」ブロックにデータを作成するメソッドをbefore(:each)
呼び出さなくても、サンプルグループごとにデータをシードするために使用しlet
ます。この場合、「it」ブロック内のコードは少なくなります。
let
いくつかの例で一部のデータが必要な場合に使用します。
beforeとletはどちらも「it」ブロックを乾燥させるのに最適です。
混乱を避けるために、「let」はと同じではありませんbefore(:all)
。「Let」は、各例(「it」)のメソッドと値を再評価しますが、同じ例の複数の呼び出しにわたって値をキャッシュします。詳しくは、https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-letをご覧ください。
ここで反対意見:5年のrspecの後、私はあまり好きではありませんlet
。
セットアップで宣言されているものの一部が実際には状態に影響を与えていない場合、セットアップに理由を付けるのは難しくなります。
結局、欲求不満から、誰かが仕様を機能させるために(遅延評価なしで同じこと)に変更let
しlet!
ます。これがうまくいくと、新しい習慣が生まれます:新しい仕様が古いスイートに追加されて機能しない場合、作者が最初に試みるのはランダムなlet
呼び出しに前髪を追加することです。
間もなく、パフォーマンス上のメリットはすべてなくなります。
私は、rspecのトリックよりも、Rubyをチームに教えたいと思っています。インスタンス変数またはメソッド呼び出しは、このプロジェクトおよび他のあらゆる場所で役立ちlet
ます。構文は、rspecでのみ役立ちます。
let()
何度も作成したくない高価な依存関係に適しています。またsubject
、と適切にペアリングされ、複数の引数を持つメソッドへの繰り返しの呼び出しをドライアップできます。
高価な依存関係が何度も繰り返され、署名が大きいメソッドは、どちらもコードを改善できるポイントです。
これらすべてのケースで、rspecマジックの鎮静剤を使用して困難なテストの症状に対処するか、原因に対処することができます。過去数年間を前者に費やしすぎたような気がしますが、今はもっと良いコードが欲しいです。
元の質問に答えるには、私はしたくないのですが、それでも使用しますlet
。私はほとんどの場合、チームの他のメンバーのスタイルに合わせるために使用します(世界中のほとんどのRailsプログラマーは今やrspecのマジックに深く関わっているようです)。制御できないコードにテストを追加するときや、より適切な抽象化にリファクタリングする時間がないとき、つまり、唯一のオプションが鎮痛剤であるとき、それを使用することがあります。
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
これにより、仕様がクリーンで読みやすくなります。