RSpecの件名とletの違いは何ですか?いつ使用すべきかどうか?


86

http://betterspecs.org/#subjectには、とに関する情報がsubjectありletます。しかし、私はそれらの違いについてはまだはっきりしていません。さらに、SOの投稿RSpecテストでbefore、let、subjectを使用することに反対する議論は何ですか?subjectまたはを使用しない方が良いと述べましたlet。どこに行こうか?私はとても混乱しています。

回答:


191

概要:RSpecのサブジェクトは、テストされるオブジェクトを参照する特別な変数です。期待値は暗黙的に設定でき、1行の例をサポートします。一部の慣用的なケースでは読者には明らかですが、それ以外の場合は理解が難しく、避ける必要があります。RSpecのlet変数は、怠惰にインスタンス化された(メモ化された)変数です。それらは主題ほど従うのは難しいことではありませんが、それでももつれたテストにつながる可能性があるので、慎重に使用する必要があります。

件名

使い方

サブジェクトはテスト対象のオブジェクトです。RSpecは主題について明確な考えを持っています。定義されている場合とされていない場合があります。そうである場合、RSpecは明示的に参照せずにメソッドを呼び出すことができます。

デフォルトでは、最も外側のサンプルグループ(describeまたはcontextブロック)の最初の引数がクラスである場合、RSpecはそのクラスのインスタンスを作成し、それをサブジェクトに割り当てます。たとえば、次のパスがあります。

class A
end

describe A do
  it "is instantiated by RSpec" do
    expect(subject).to be_an(A)
  end
end

あなたはあなた自身で主題を定義することができますsubject

describe "anonymous subject" do
  subject { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

サブジェクトを定義するときに、サブジェクトに名前を付けることができます。

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(a).to be_an(A)
  end
end

件名に名前を付けても、匿名で参照できます。

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

複数の名前付きサブジェクトを定義できます。最近定義された名前付きサブジェクトはanonymoussubjectです。

ただし、主題は定義されていますが、

  1. 怠惰にインスタンス化されます。つまり、記述されたクラスの暗黙的なインスタンス化または渡されたブロックの実行は、例で指定されたサブジェクトが参照されるsubjectまで発生しませんsubject。(グループ内の例が実行される前に)明示的なサブジェクトを熱心にインスタンス化する場合は、のsubject!代わりに言いますsubject

  2. 期待値は暗黙的に設定できます(書き込みsubjectや名前付きサブジェクトの名前なしで):

    describe A do
      it { is_expected.to be_an(A) }
    end
    

    サブジェクトは、この1行の構文をサポートするために存在します。

いつ使用するか

暗黙的subject(サンプルグループから推測)は、次の理由で理解するのが困難です。

  • 舞台裏でインスタンス化されます。
  • 暗黙的に(is_expected明示的なレシーバーなしで呼び出すことによって)使用されるか、明示的に(asとしてsubject)使用されるかにかかわらず、期待値が呼び出されているオブジェクトの役割または性質に関する情報を読者に提供しません。
  • ワンライナーの例の構文には例の説明(it通常の例の構文ではの文字列引数)がないため、例の目的について読者が持っている唯一の情報は期待そのものです。

したがって、コンテキストがすべての読者に十分に理解されている可能性が高く、説明の例が実際には必要ない場合にのみ、暗黙のサブジェクトを使用することが役立ちます。標準的なケースは、shouldaマッチャーを使用してActiveRecord検証をテストすることです。

describe Article do
  it { is_expected.to validate_presence_of(:title) }
end

明示的な匿名subjectsubject名前なしで定義)は、読者がインスタンス化された方法を確認できるため、少し優れていますが、

  • それでも、サブジェクトのインスタンス化を、それが使用されている場所から遠くに置くことができます(たとえば、それを使用する多くの例があるサンプルグループの上部にあります)。
  • 暗黙のサブジェクトが行う他の問題があります。

名前付きサブジェクトは意図を明らかにする名前を提供しletますが、変数の代わりに名前付きサブジェクトを使用する唯一の理由は、匿名サブジェクトを時々使用したい場合であり、匿名サブジェクトが理解しにくい理由を説明しました。

したがって、明示的な匿名subjectまたは名前付きの件名の合法的な使用は非常にまれです。

let 変数

それらがどのように機能するか

let 変数は、2つの違いを除いて、名前付きサブジェクトと同じです。

  • それらはで定義されているlet/let!の代わりにsubject/subject!
  • 匿名を設定しsubjectたり、暗黙的に期待を呼び出すことを許可したりしません。

いつ使用するか

let例間の重複を減らすために使用することは完全に合法です。ただし、テストの明確さを犠牲にしない場合にのみ行ってください。使用するのに最も安全な時期letは、let変数の目的がその名前から完全に明確であり(したがって、読者が各例を理解するために、何行も離れている可能性がある定義を見つける必要がない)、同じように使用される場合です。すべての例で。これらのいずれかが当てはまらない場合は、単純な古いローカル変数でオブジェクトを定義するか、例の中でファクトリメソッドを呼び出すことを検討してください。

let!それは怠惰ではないので、危険です。誰かがを含むサンプルグループにサンプルを追加したlet!が、サンプルにlet!変数が必要ない場合、

  • 読者はlet!変数を見て、それが例に影響を与えるかどうか、またどのように影響するのか疑問に思うため、その例は理解しにくいでしょう。
  • let!変数の作成に時間がかかるため、例は必要以上に遅くなります

したがってlet!、仮にあったとしても、将来のサンプル作成者がその罠に陥る可能性が低い、小さくて単純なサンプルグループでのみ使用してください。

例ごとの単一の期待フェチ

let個別に説明する価値のある主題または変数の一般的な乱用があります。一部の人々はこのようにそれらを使用するのが好きです:

describe 'Calculator' do
  describe '#calculate' do
    subject { Calculator.calculate }
    it { is_expected.to be >= 0 }
    it { is_expected.to be <= 9 }
  end
end

(これは、2つの期待値が必要な数値を返すメソッドの単純な例ですが、メソッドが多くの期待値を必要とする、および/または多くの副作用があるより複雑な値を返す場合、このスタイルにはさらに多くの例/期待値があります。すべて期待が必要です。)

人々は、例ごとに1つの期待値しか持たない(例ごとに1つのメソッド呼び出しのみをテストする必要があるという有効なルールと混同されている)と聞いたため、またはRSpecのトリッキーさに恋をしているためにこれを行います。匿名または名前付きの件名またはlet変数を使用するかどうかにかかわらず、それを行わないでください!このスタイルにはいくつかの問題があります。

  • 匿名のサブジェクトは例のサブジェクトではありません—メソッドがサブジェクトです。このようにテストを書くと、言語が台無しになり、考えるのが難しくなります。
  • 一行の例でいつもそうであるように、期待の意味を説明する余地はありません。
  • 主題はそれぞれの例のために構築されなければならず、それは遅いです。

代わりに、次の1つの例を記述します。

describe 'Calculator' do
  describe '#calculate' do
    it "returns a single-digit number" do
      result = Calculator.calculate
      expect(result).to be >= 0
      expect(result).to be <= 9
    end
  end
end

17
うわー!+💯!「例ごとの単一の期待フェチ」は、いわば金でその重みの価値があります。私はその(誤った)規則に非常に厳密に従う必要性を感じたことはありませんが、今では私たちの他の人々にそれを強制しようとする人々に対して十分な武装をしています。ありがとう!
iconoclast 2017

2
また、multi-expectationブロックですべてのexpect行を実行する場合(最初の行が失敗した場合に実行されないのではなく)、次の:aggregate_failuresような行でタグを使用できますit ​"marks a task complete"​, ​:aggregate_failures​ ​do(Rails 5 Test Prescriptionsの本から引用)
labyrinth

1
私はすべて誇大広告やフェチにも反対です。「例ごとの単一の期待」は、主に、多くの無関係な期待を1つの例にまとめるのが好きな人向けです。意味的に言えば、この例は理想的ではありません。1桁の数値を検証せず、[0、9]の実数を検証するためです。これは、驚くべきことに、はるかに読みやすい1つの期待値でコーディングできますexpect(result).to be_between(0, 9)
アンドレフィ

1桁の数値(Int [0,9])のみを検証する場合は、より適切ですexpect(result.to_s).to match(/^[0-9]$/)-醜いことはわかっていますが、実際にあなたが言っていることをテストするか、おそらくbetween+を使用しis_a? Integerますが、ここではテストしていますタイプも。そして、ただlet..それは懸念の対象ではないはずです、そして実際には例の間で値を再評価する方が良いかもしれません。それ以外の場合は、投稿に+1
アンドレフィ

let!は自分のチームメートを自分で納得させることの危険性についてのあなたの説明が大好きです。この回答を送信します。
DamonAw19年

5

Subjectそしてletあなたあなたのテスト整頓アップと速度を支援するだけのツールです。rspecコミュニティの人々はそれらを使用しているので、それらを使用しても大丈夫かどうかについて心配する必要はありません。それらは同様に使用できますが、わずかに異なる目的を果たします

Subjectテスト対象を宣言し、その後、それを次のテストケースにいくつでも再利用できます。これにより、コードの繰り返しが減ります(コードの乾燥)

Let に代わるものです before: eachテストデータをインスタンス変数に割り当てるブロックです。 Letいくつかの利点があります。まず、インスタンス変数に割り当てずに値をキャッシュします。次に、遅延評価されます。つまり、仕様で要求されるまで評価されません。したがってlet、テストをスピードアップするのに役立ちます。またlet、読みやすいと思います


1

subjectテスト対象であり、通常はインスタンスまたはクラスです。letテストで変数を割り当てるためのものであり、インスタンス変数を使用するのではなく、遅延して評価されます。このスレッドにはいくつかの良い例があります。

https://github.com/reachlocal/rspec-style-guide/issues/6

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