web-dev-qa-db-ja.com

RSpec:letブロックとbeforeブロックの違いは何ですか?

RSpecのletブロックとbeforeブロックの違いは何ですか?

そして、それぞれをいつ使用するのですか?

以下の例で良いアプローチは何ですか(前または前)?

let(:user) { User.make !}
let(:account) {user.account.make!}

before(:each) do
 @user = User.make!
 @account = @user.account.make!
end

私はこれを研究しました stackoverflow post

しかし、上記のような関連のものにletを定義するのは良いことですか?

86
kriysna

人々は、それらが異なる基本的な方法のいくつかを説明したように見えますが、before(:all)を省き、それらが使用されるべき理由を正確に説明しません。

this answer に記載されている理由もあり、インスタンス変数は大部分の仕様で使用されている場所がないと考えているため、ここではオプションとして言及しません。

letブロック

letブロック内のコードは、参照されたときにのみ実行されます。これを遅延ロードすると、これらのブロックの順序は無関係になります。これにより、スペックを介して繰り返しセットアップを削減するための大量の電力が提供されます。

この1つの(非常に小さくて不自然な)例は次のとおりです。

_let(:person)     { build(:person) }
subject(:result) { Library.calculate_awesome(person, has_moustache) }

context 'with a moustache' do
  let(:has_moustache) { true } 
  its(:awesome?)      { should be_true }
end

context 'without a moustache' do
  let(:has_moustache) { false } 
  its(:awesome?)      { should be_false }
end
_

_has_moustache_がそれぞれの場合で異なるように定義されていることがわかりますが、subjectの定義を繰り返す必要はありません。重要なことは、現在のコンテキストで定義されている最後のletブロックが使用されることです。これは、大部分の仕様で使用されるデフォルトを設定するのに適しています。必要に応じて上書きできます。

たとえば、_calculate_awesome_をtrueに設定してpersonモデルを渡した場合、_top_hat_の戻り値をチェックしますが、口ひげはありません。

_context 'without a moustache but with a top hat' do
  let(:has_moustache) { false } 
  let(:person)        { build(:person, top_hat: true) }
  its(:awesome?)      { should be_true }
end
_

Letブロックについて注意すべきもう1つの点は、データベースに保存されているもの(つまり、Library.find_awesome_people(search_criteria))を検索する場合は使用しないでください。参照されました。 _let!_またはbeforeブロックはここで使用されるべきものです。

また、nevereverbeforeを使用してletブロックの実行をトリガーします、これが_let!_の目的です!

let!ブロック

_let!_ブロックは、定義された順序で実行されます(beforeブロックのように)。 beforeブロックとの主な違いは、インスタンス変数にフォールバックする必要がなく、この変数への明示的な参照を取得することです。

letブロックと同様に、複数の_let!_ブロックが同じ名前で定義されている場合、最新のものが実行時に使用されます。コアの違いは、_let!_ブロックがこのように使用されると複数回実行されるのに対し、letブロックは最後にのみ実行されることです。

before(:each)ブロック

before(:each)はブロック前のデフォルトであるため、毎回完全なbefore(:each) {}を指定するのではなく、_before {}_として参照できます。

いくつかのコアな状況でbeforeブロックを使用することが私の個人的な好みです。次の場合、ブロックの前に使用します。

  • モック、スタブ、またはダブルを使用しています
  • 適切なサイズのセットアップがあります(通常、これは工場の特性が正しくセットアップされていないサインです)
  • 直接参照する必要はありませんが、セットアップには必要な変数がいくつかあります
  • 私はRailsで機能的なコントローラーテストを書いています、そしてテストするために特定のリクエストを実行したいと思います(すなわち_before { get :index }_)。多くの場合、これにsubjectを使用できますが、参照が不要な場合は、より明確に感じることがあります。

仕様のために大きなbeforeブロックを記述していることに気付いた場合は、工場を調べて、特性とその柔軟性を完全に理解していることを確認してください。

before(:all)ブロック

これらは、現在のコンテキスト(およびその子)の仕様の前に一度だけ実行されます。これらは実行と労力を削減できる特定の状況があるため、正しく記述されていれば非常に有利に使用できます。

1つの例(実行時間にほとんど影響しない)は、テストのためにENV変数をモックアウトすることです。これは一度だけ実行する必要があります。

それが役立つことを願っています:)

134
Jay

ほとんどの場合、letを好みます。リンクする投稿は、letも高速であることを指定しています。ただし、多くのコマンドを実行する必要がある場合は、before(:each)を使用できます。これは、多くのコマンドが関係する場合に構文がより明確になるためです。

あなたの例では、before(:each)の代わりにletを使用することをお勧めします。一般的に、いくつかの変数の初期化が完了すると、letを使用する傾向があります。

25
Spyros

言及されていない大きな違いは、letで定義された変数は、最初に呼び出すまでインスタンス化されないということです。 before(:each)ブロックはすべての変数をインスタンス化しますが、letは複数のテストで使用する可能性のあるいくつかの変数を定義できますが、自動的にはインスタンス化しません。これを知らずに、すべてのデータが事前にロードされることを期待している場合、テストは戻って自分に噛み付く可能性があります。場合によっては、いくつかのlet変数を定義し、before(:each)ブロックを使用して各letインスタンスを呼び出して、データが使用可能であることを確認することもできます。そもそも.

15
mikeweber

Machinistを使用しているようです。 makeにいくつかの問題があることに注意してください!グローバルなフィクスチャトランザクションの外部で発生するlet(非ビッグバージョン)の内部(トランザクションフィクスチャも使用している場合)で、他のテストのデータが破損します。

3
Tim Connor