shared_examples
と shared_context
は実装的には同じものですが、
個人的にはdescribeがテスト対象の明示、contextがテスト条件の明示で使い分けるのと同じように以下のルールでやっています。
- itを共通化したいときは
shared_examples
- contextを共通化したいときは
include_context
- describeは共通化しない
shared_examples の使い方
Shared examplesのRSpec公式のドキュメント を見てみましょう。
RSpec.shared_examples "some example" do |parameter|
\# Same behavior is triggered also with either `def something; 'some value'; end`
\# or `define_method(:something) { 'some value' }`
let(:something) { parameter }
it "uses the given parameter" do
expect(something).to eq(parameter)
end
end
RSpec.describe SomeClass do
include_examples "some example", "parameter1"
include_examples "some example", "parameter2"
end
このようにinclude_examplesをすることで、テストする条件をまとめることができます。
include_examples は it_behaves_like と書いてもokです。
公式ドキュメントがわかりやすいですね!
わかりやすいので、shared_examplesばかり使われている気がするぞ。。。
shared_context
次にshared_context。
以下はShared contextのRSpec公式のドキュメント の例を短く改変しています。*1
RSpec.shared_context "shared stuff" do
subject { xxx }
before do
create(:stuff)
end
end
require "./shared_stuff.rb"
RSpec.describe "group that includes a shared context using 'include_context'" do
include_context "shared stuff"
it "accesses the subject defined in the shared context" do
expect(subject).to eq('this is the subject')
end
end
個人的に上記のような shared_context
はあまり好きではありません。
テスト対象であるsubject
やshared_method
がshared stuff
の中に隠蔽されてしまっていて、何をテストしているかがわかりにくいからです。
私が書くなら以下のようになるでしょう。
RSpec.shared_context "shared stuff" do
before do
create(:stuff)
end
end
require "./shared_stuff.rb"
RSpec.describe "group that includes a shared context using 'include_context'" do
subject { nanika }
include_context "shared stuff"
it "accesses the subject defined in the shared context" do
expect(subject).to eq('this is the subject')
end
end
「subjectを毎回書かなくちゃいけない!」と思うかもしれませんが、テスト対象は毎回定義しません?
じゃないと、何をテストしているかわからなくなってしまいます。
もちろんshared_contextに引数で毎回差し込むこともできるのですが、それだとsubjectを書くのと何も変わらないので、subjectは毎回書くようにしています。
うん。わかりやすい。
まとめ
shared_exampleはsystem specの権限管理的や暗黙的にテストしておきたいときに自動で発火するように仕込むのに便利です。*2
ちょっとしたテストのDRYのために、shared_exampleを使ってしまうとテストコードが変更に弱くなってしまうので注意しましょう。
一方shared_contextは「ある条件のユーザーを作る」や「ユーザー1でログインする」など再現性のある「条件」を作成するのに使うと便利です。
shared_contextはテスト条件を再利用するのに便利ですので、ちょっとしたテストのDRYのために使うことも多いでしょう。
このときに先述のsubjectを隠蔽するようなことをやってしまうと、私の経験上厄介なことになります。 *3
AAA(Arrange Act Assert)の考え方を知ってる人には、「あーAssertをshared_examplesで共通化して、Arrangeをshared_contextで共通化して、Actはベタで書くのね。なるほどね〜」となったと思います。
*1 shared_stuff.rbに shared_context 'shared stuff'
が定義が省略されているので、それを個人的に解釈して補完して追記。
*2 暗黙的にテストしたい場合は、metadata を利用したものが便利です
*3 その他には shared_contextもshared_examplesも別ファイルで管理することになるので、具体的な名前をつけたほうがいいよ!ってtwadaさんが言ってたよ! RSpec3で書かれた jnchito さんの記事 もあります。)