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


90

違いは何であるletbeforeRSpecの中のブロックは?

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

以下の例で良い方法は何ですか(以前に)

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

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

私はこのスタックオーバーフローの投稿を勉強しました

しかし、上記のようなアソシエーション関連のletを定義するのは良いことでしょうか?


1
基本的に、「let」はインスタンス変数が嫌いな人が使用します。補足として、FactoryGirlまたは同様のツールの使用を検討する必要があります。
11年

回答:


146

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

インスタンス変数は、この回答で述べられている理由の一部が原因で、仕様の大部分で使用されていないため、ここではそれらをオプションとして言及しません。

ブロックしましょう

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、現在のコンテキストで定義されている最後のブロックが使用されることです。これは、大部分の仕様で使用されるデフォルトを設定するのに適しています。必要に応じて上書きできます。

たとえば、trueに設定されcalculate_awesomepersonモデルが渡された場合の戻り値をチェック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ブロックはここで使用されるべきものです。

また、ブロックの実行をトリガーするために使用することは決してありません。これが目的です。beforeletlet!

しましょう!ブロック

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

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

before(:each)ブロック

before(:each)はデフォルトのbeforeブロックなのでbefore {}before(:each) {}毎回完全を指定するのではなく参照することができます。

beforeいくつかの中核的な状況でブロックを使用することが私の個人的な好みです。以下の場合、beforeブロックを使用します。

  • 私はモック、スタブ、ダブルスを使用しています
  • 適切なサイズの設定があります(通常、これは工場の特性が正しく設定されていないことを示す兆候です)
  • 直接参照する必要のない変数がいくつかありますが、設定には必須です
  • Railsで機能コントローラーテストを作成していて、テストする特定の要求を実行したい(つまりbefore { get :index })。subject多くの場合にこれを使用できますが、参照が必要ない場合は、より明示的に感じることがあります。

before仕様に合わせて大きなブロックを作成している場合は、工場をチェックして、特性とその柔軟性を完全に理解していることを確認してください。

before(:all)ブロック

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

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

それが役に立てば幸い:)


私と同じですが、このQ&AのRSpecタグに気づかなかったミニテストユーザーの場合、before(:all)オプションはMinitestに存在しません。コメントの回避策は次のとおりです
MrYoshiji

参照されている記事はリンク
Dylan Pierce

@DylanPierceに感謝します。その記事のコピーが見つからないため、代わりにこれに対処するSO回答を参照しました:)
Jay

ありがとうございました:)とても感謝しています
ディラン・ピアス

1
itsrspec-coreにはありません。より近代的で慣用的な方法はit { is_expected.to be_awesome }です。
ズビン

26

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

あなたの例では、私は間違いletなくの代わりに使用したいと思いますbefore(:each)。一般的に言って、変数の初期化だけを行う場合、私はを使用する傾向がありletます。


15

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


20
を使用let!して、各例の前に呼び出されるメソッドを定義できます。RSpec docsを参照してください。
ジムスチュワート

3

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.