1. ホーム
  2. ruby

[解決済み] RSpecのsubjectとletの違いは何ですか?どんな時に使うのか、使わないのか?

2022-12-20 12:35:24

質問

http://betterspecs.org/#subject に関するいくつかの情報があります。 subjectlet . しかし、その違いについては、まだはっきりしません。さらに、SOのポスト RSpecのテストでbefore, let, subjectを使用することに対する反論は? は、どちらも使わないほうがいいと言っています。 subject または let . どこに行こうか?私はとても混乱しています。

どのように解決するのですか?

概要 RSpecのsubjectは、テストされるオブジェクトを参照する特別な変数です。期待値は暗黙のうちにそれに設定することができ、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

名前付きサブジェクトは複数定義することができます。最も新しく定義された名前付きサブジェクトは、匿名 subject .

主語がどのように定義されていても

  1. 遅延的にインスタンス化される。つまり、記述されたクラスの暗黙のインスタンス化、あるいは、渡されたブロックの実行により subject が実行されるまでは起こりません。 subject を実行するか、または名前付きサブジェクトがサンプルで参照されるまで起こりません。もし、説明的なサブジェクトを (そのグループ内の例が実行される前に) 熱心にインスタンス化したい場合は、次のようにします。 subject! の代わりに subject .

  2. 期待値を暗黙のうちに設定することができます( subject や名前付きサブジェクトの名前を書かずに)。

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

    この一行構文をサポートするために、主語が存在します。

いつ使うか

暗黙の subject (例のグループから推測される) は、以下の理由で理解しにくいです。

  • 裏でインスタンス化されている
  • 暗黙的に使用されるかどうか ( is_expected を呼び出すことで)、あるいは明示的に(受信者を明示的に指定せずに subject のように)、期待値が呼び出されているオブジェクトの役割や性質についての情報を読者に与えません。
  • ワンライナー例の構文には、例の記述がありません(文字列引数の it への文字列引数) を持たないので、読者が持つ例の目的に関する唯一の情報は、予想そのものです。

したがって を使用するのは、文脈がすべての読者によく理解され、例の説明が本当に必要でない場合にのみ有用です。 . 典型的なケースは、ActiveRecordのバリデーションをshoulda matcherでテストすることです。

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

説明的な匿名 subject (で定義される)。 subject で定義されたもの) は、読者がどのようにインスタンス化されるかを見ることができるので、少しは良いのですが

  • は、サブジェクトのインスタンス化をそれが使用される場所から遠くに置くことができ (たとえば、それを使用する多くの例がある例グループの一番上に)、これはまだフォローするのが難しいですし
  • 暗黙の主語が持つ他の問題点を抱えています。

名前付き主語は意図を明らかにする名前を提供しますが、名前付き主語を使用する唯一の理由は let という変数は、匿名主語を使いたい場合に使うものです。

では の正当な使い方は、明示的な匿名 subject や名前付きサブジェクトは非常に稀です。 .

let 変数

どのように動作するか

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

  • で定義されていることです。 let / let! の代わりに subject / subject!
  • を設定しない。 subject を設定したり、期待値が暗黙のうちに呼び出されることを許したりしません。

いつ使うか

を使うのは全く正当なことです。 let を使うのは全く正当なことです。しかし、テストの明確さを犠牲にしないときだけそうしてください。 を使用する最も安全なタイミングは let を使用する最も安全なタイミングは let 変数の目的がその名前から完全に明らかであり(読者が各例を理解するために何行も離れた定義を見つける必要がないように)、すべての例で同じ方法で使用されている場合です。もしそのどちらかが当てはまらない場合は、古いローカル変数でオブジェクトを定義するか、例の中でファクトリーメソッドを直接呼び出すことを検討してください。

let! はダラダラしてないので危険です。 を含む example グループに誰かが example を追加した場合、その例には let! を含む example グループに追加されましたが、その example には let! 変数は必要ありません。

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

そこで let! を使うのは、将来例を書く人がその罠にはまる可能性が低い、小さくてシンプルな例グループだけにしてください。

1 つの例に対する 1 つの期待」フェチ

よくあるのが、主語を多用したり 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