Techioz Blog

RSpec の subject と let の違いは何ですか?いつ使用する必要があるのか、それとも使用しないのか?

概要

http://betterspecs.org/#subject には、subject と let に関する情報が記載されています。しかし、それらの違いについてはまだよくわかりません。さらに、SO post RSpec テストで before、let、subject を使用することに対する議論は何ですか?主語もletも使わない方が良いとのこと。どこに行こうか?とても混乱しています。

解決策

概要: 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

複数の名前付きサブジェクトを定義できます。最も新しく定義された名前付きサブジェクトは匿名サブジェクトです。

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

暗黙の主語 (例のグループから推測される) は理解するのが難しいです。

したがって、暗黙の主語を使用するのは、文脈がすべての読者によく理解される可能性が高く、実際に例の説明が必要ない場合にのみ役立ちます。標準的なケースは、Shoulda matcher を使用して ActiveRecord 検証をテストすることです。

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

明示的な匿名サブジェクト (名前のないサブジェクトで定義) は、読者がそれがどのようにインスタンス化されるかを確認できるため、多少は優れていますが、

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

したがって、明示的な匿名サブジェクトまたは名前付きサブジェクトが正当に使用されることは非常にまれです。

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

例間の重複を減らすために 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 つだけであるべきだという話を聞いたことがあるため (これは、1 つの例につき 1 つのメソッド呼び出しのみをテストする必要があるという有効なルールと混同されています)、または RSpec のトリッキーさに惚れているためです。匿名または名前付きサブジェクト、または let 変数のいずれを使用する場合でも、このようなことは行わないでください。このスタイルにはいくつかの問題があります。

代わりに、単一の例を作成します。

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