要約
-
include_context
とinclude_examples
はエイリアスの関係。動作は変わらない。 -
include_(context|examples)
は、現在のコンテキストに直接テストケースを埋め込む。 -
it_behaves_like
は、新しいコンテキストを自動生成して、そこにテストケースを埋め込む。 - 基本的に it_behaves_like を使った方が良い。
include_context の動作
include_context は、 現在のコンテキストに直接テストケースを埋め込みます。
……え、意味が分からない? 大丈夫、私もドキュメントの "include the examples in the current context" という解説を初めて見たときは理解できませんでした。
言葉では説明しづらいので、コードで説明します。
例えば、以下のようにコードを記述したとき
# 共有したい example を定義
shared_examples 'test1' do
it 'something が 1 であること' do
expect(something).to eq 1
end
end
# テストを記述
describe 'hoge' do
let(:something) { 1 }
include_context 'test1'
end
実行時には以下のように解釈されます。
describe 'hoge' do
let(:something) { 1 }
# include_context 'test1' の部分に直接埋め込まれる
it 'something が 1 であること' do
expect(something).to eq 1
end
end
include_examples の動作
include_examples は、 include_context のエイリアス(別名)です。
つまり、 include_examples の動作は include_context と全く同じです。
どうして同じ機能が別々の名前で用意されているのかというと、 rspec は自然言語(人間が普段つかう言葉)に近い文章でテストが書けることを目指して作られているからです。
「人間が読んだ時に、より理解しやすい方を選んでね」って意図で、複数の名前が定義されているんですね。
rspec は他にも、違う名前で同じ振る舞いをするものが沢山あります。
it_behaves_like の動作
it_behaves_like は、 新しいコンテキストを自動生成して、そこにテストケースを埋め込みます。
これもコードで見た方が分かりやすいです。
以下のようにコードを記述すると、
# 共有したい example を定義
shared_examples 'test1' do
it 'something が 1 であること' do
expect(something).to eq 1
end
end
# テストを記述
describe 'hoge' do
let(:something) { 1 }
it_behaves_like 'test1'
end
実行時には以下のように解釈されます。
describe 'hoge' do
let(:something) { 1 }
# it_behaves_like 'test1' の部分に、自動で新しい context が生成される
context 'behaves like a test1' do
it 'something が 1 であること' do
expect(something).to eq 1
end
end
end
include_(context|examples) の罠
一見、直接埋め込もうが、自動でコンテキストを挿入しようが、たいした違いはなさそうに思えます。
しかし、実は include_(context|examples) には、「定義の上書き」と呼ばれる(というか僕が勝手にそう呼んでいる)罠があるのです。
以下のようなテストコードを考えてみましょう。
shared_examples 'test1' do
let(:something) { 1 }
it 'something が 1 であること' do
expect(something).to eq 1
end
end
shared_examples 'test2' do
let(:something) { 2 }
it 'something が 2 であること' do
expect(something).to eq 2
end
end
describe 'hoge' do
include_context 'test1'
include_context 'test2'
end
上記のテストは、実行時には以下のように解釈されます。
describe 'hoge' do
let(:something) { 1 }
it 'something が 1 であること' do
expect(something).to eq 1
end
let(:something) { 2 }
it 'something が 2 であること' do
expect(something).to eq 2
end
end
同じコンテキストに2つの let が埋め込まれることで、片方の定義が上書きされてしまい、「something が 1 であること」のテストが失敗します。
include_(context|example) をメソッド感覚で使うと、上記のようなミスを犯してしまいがちです。
一方、 it_behaves_like ならば、
describe 'hoge' do
context 'behaves like a test1' do
let(:something) { 1 }
it 'something が 1 であること' do
expect(something).to eq 1
end
end
context 'behaves like a test2' do
let(:something) { 2 }
it 'something が 2 であること' do
expect(something).to eq 2
end
end
end
のように context が分離されるため、安全です。
結局、どう使い分ければいいの?
include_(context|examples) には上述したような罠があるので、 常に it_behaves_like を使えばいいと思います。
「include_(context|examples) じゃないと困る」という場面はありません。(断言)
参考資料