Help us understand the problem. What is going on with this article?

【RSpec】shared_examplesを使ってテストをもう少しスリムにする

RSpecでテストを書く際、複数のテストグループにおいて同じexampleを使い回したくなるケースがあるかと思います。
そんな時にはshared_examplesを使うとテストがよりスッキリします。

shared examplesとは

公式によると、

Shared examples let you describe behaviour of classes or modules. When declared,
a shared group's content is stored. It is only realized in the context of
another example group, which provides any context the shared group needs to
run.

ざっくりいうと、
Shared examplesではクラスやモジュールの振る舞いをまとめて定義できる。
定義したshared exampleは他のexampleグループから呼び出すことができる。

shared_examplesは下記のような感じで共通化したいexampleを記載します。

shared_example "exampleのタイトル" do
  expect(...)
end

定義したshared examplesは、下記のように呼び出して使います。

describe Model do
    # shared_exampleをincludeする
    include_examples "#{exampleのタイトル}"
    context 'こんな場合' do
      # shared_exampleで定義した結果と同じになるとテストがgreenになる
      it_behaves_like "#{exampleのタイトル}"
    end
end

これによりexample(it~do~end)を共通部品として扱うことができるようになり、複数のテストグループで同じような記述をする手間が省けます(&コードがよりDRYになります)。

ざっくり使い方

例えば引数にstring型の値を2つ取り、戻り値として文字数が多い方の引数の値を返すメソッドがあったとします。
*簡略化のため、今回は共に同じ長さの文字列が引数で渡された場合には、第一引数の値を返すものとします。

def longer_message(x, y)
  (x.size > y.size) ?  x : y
end

これに対するテストをそのままベタ書いたら下記のような感じになります。
*厳密性よりも雰囲気を感じ取っていただければと。

RSpec.describe 'longer_messageのテスト' do
  context '第一引数の方が長い場合' do
    context '引数の値が共に1文字以上の場合' do
      let(:x){'ほげら'}
      let(:y){'ふが'}
      it '第一引数の値が返されるはず' do
        expect(longer_message(x, y)).to eq x
      end
    end
    context 'yの値が空欄の場合' do
      let(:x){'ほげら'}
      let(:y){''}
      it '第一引数の値が返されるはず' do
        expect(longer_message(x, y)).to eq x
      end
    end
  end

  context '第二引数の方が長い場合' do
    context '引数の値が共に1文字以上の場合' do
      let(:x){'ふが'}
      let(:y){'ほげら'}
      it '第二引数の値が返されるはず' do
        expect(longer_message(x, y)).to eq y
      end
    end
    context 'xの値が空欄の場合' do
      let(:x){''}
      let(:y){'ほげら'}
      it '第二引数の値が返されるはず' do
        expect(longer_message(x, y)).to eq y
      end
    end
  end

  context '引数の長さが同じ場合' do
    context '引数の値が共に1文字以上の場合' do
      let(:x){'ふが'}
      let(:y){'ほげ'}
      it '第一引数の値が返されるはず' do
        expect(longer_message(x, y)).to eq x
      end
    end
    context '引数の値が共に空文字の場合' do
      let(:x){''}
      let(:y){''}
      it '第一引数の値が返されるはず' do
        expect(longer_message(x, y)).to eq x
      end
    end
  end
end

この場合、各テストケースによってメソッドに渡される引数の値は異なりますが、example自体はexpect(longer_message(x, y)).to eq x またはexpect(longer_message(x, y)).to eq yの2通りしかありません。

同じ結果を何回も直接書くよりも、まとめた方がスッキリするよね(あと、修正とかも一箇所で済むよね)ということで、shared examplesを使ってみます。
*厳密性よりも雰囲気を感じ取っていただければと。

# 共通化したいexampleを記載する
shared_examples "第一引数の値が返されるはず" do
  expect(longer_message(x, y)).to eq x
end

# 共通化したいexampleを記載する
shared_examples "第二引数の値が返されるはず" do
  expect(longer_message(x, y)).to eq y
end

describe 'longer_messageのテスト' do
   # 定義したshared exampleをincludeする
  include_examples "第一引数の値が返されるはず"
  include_examples "第二引数の値が返されるはず"

  context '第一引数の方が長い場合' do
    context '引数の値が共に1文字以上の場合' do
      let(:x){'ほげら'}
      let(:y){'ふが'}
      # it_behaves_like "#{shared_examplesのタイトル名}"
      it_behaves_like "第一引数の値が返されるはず"
    end
    context 'yの値が空欄の場合' do
      let(:x){'ほげら'}
      let(:y){''}
      it_behaves_like "第一引数の値が返されるはず"
    end
  end

  context '第二引数の方が長い場合' do
    context '引数の値が共に1文字以上の場合' do
      let(:x){'ふが'}
      let(:y){'ほげら'}
      it_behaves_like "第二引数の値が返されるはず"
    end
    context 'xの値が空欄の場合' do
      let(:x){''}
      let(:y){'ほげら'}
      it_behaves_like "第二引数の値が返されるはず"
    end
  end

  context '引数の長さが同じ場合' do
    context '引数の値が共に1文字以上の場合' do
      let(:x){'ふが'}
      let(:y){'ほげ'}
      it_behaves_like "第一引数の値が返されるはず"
    end
    context '引数の値が共に空文字の場合' do
      let(:x){''}
      let(:y){''}
      it_behaves_like "第一引数の値が返されるはず"
    end
  end
end

行数的にはあまり減っていませんが、テストの見栄えはスッキリしたかなと思います。
また、exampleの修正が発生した場合でも、定義元のshared_examplesを変更するだけで良いので、メンテナンス性は向上するかなと思います。

参考

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away