[解決済み] RSpecのsubjectとletの違いは何ですか?どんな時に使うのか、使わないのか?
質問
http://betterspecs.org/#subject
に関するいくつかの情報があります。
subject
と
let
. しかし、その違いについては、まだはっきりしません。さらに、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
.
主語がどのように定義されていても
-
遅延的にインスタンス化される。つまり、記述されたクラスの暗黙のインスタンス化、あるいは、渡されたブロックの実行により
subject
が実行されるまでは起こりません。subject
を実行するか、または名前付きサブジェクトがサンプルで参照されるまで起こりません。もし、説明的なサブジェクトを (そのグループ内の例が実行される前に) 熱心にインスタンス化したい場合は、次のようにします。subject!
の代わりにsubject
. -
期待値を暗黙のうちに設定することができます(
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
関連
-
[解決済み] フェイク、モッキング、スタビングの違いとは?
-
[解決済み] rake db:migrate db:reset とdb:schema:loadの違いについて
-
[解決済み] イコール、エクル、==、==の違いは何ですか?
-
[解決済み] Rubyのincludeとrequireの違いは何ですか?
-
[解決済み] Jestの'it'と'test'の違いは何ですか?
-
[解決済み] Rubyの理想的なプロジェクト構造
-
[解決済み] 変数名を使ったRubyの正規表現
-
[解決済み] Rubyで既存のハッシュに追加する方法
-
[解決済み] 配列要素のインデックスを O(n) よりも高速に取得する。
-
[解決済み] raise "foo"`と`raise Exception.new("foo")` の違いは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Gemfileでrubygemを指定するときの~>と>=の違いは何ですか?
-
[解決済み] Rubyで空のファイルを作成する:"touch "と同等?
-
[解決済み] ハッシュの配列をハッシュの値でソートするにはどうしたらいいですか?
-
[解決済み] Rubyでコンソールから入力を読み込む?
-
[解決済み] 今月の名前(Date.today.monthをnameに変換)。
-
[解決済み] ruby システムコマンドの終了コードチェック
-
[解決済み] bundler vs RVM vs gems vs RubyGems vs gemsets vs system ruby [closed].
-
[解決済み] 配列要素のインデックスを O(n) よりも高速に取得する。
-
[解決済み] Rubyの基本的なプロジェクトはどのように設定するのですか?
-
[解決済み] すべての文字と数字の配列を生成する