dreamin' blog

Twitter眺めていると楽しい機能の話です。

shared_examplesshared_context は実装的には同じものですが、
個人的にはdescribeがテスト対象の明示、contextがテスト条件の明示で使い分けるのと同じように以下のルールでやっています。

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 はあまり好きではありません。
テスト対象であるsubjectshared_methodshared 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 さんの記事 もあります。)