4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

共通の振る舞いのspec

Last updated at Posted at 2017-04-05

継承やmixinで振る舞いを共有するクラスがあるとき、共有する振る舞いのテストを個別に書いてたり、特定のクラスでだけ書くようでは、不十分だと思いました。

振る舞いが他の実装との兼ね合いで変わってしまったり、オーバーライドによって挙動が変わるなどしては困ります。

また、抽象クラスやモジュールでインターフェースのみ定義しておき、具象クラスでそれを実装するという仕様のとき、それが守られているかどうか確認したいことがありますが、すべてのクラスのテストでいちいち書くのも面倒だったりします。

このような共通の振る舞いのspecについて、最近はこんな感じで書いています。

共通する振る舞いを使う

こうすることで、ClassAincludeしているモジュールの想定している振る舞いが実装されていることをテストでき、また理解しやすくなります。

describe ClassA do
  # ~のように振る舞うことをテスト
  it_behaves_like 'HogeExtension', described_class.new
  it_behaves_like 'FugaExtension', described_class.new
  it_behaves_like 'HelloClass'

  # ClassA独自の振る舞いのテスト
  describe '#hello' do
    subject { described_class.new.hello }
    it { is_expected.to eq('ClassA world') }
  end
end

テスト対象

module HogeExtension
  def hoge
    'hoge'
  end
end

module FugaExtension
  def fuga
    'fuga'
  end
end

class HelloClass
  def hello
    raise NotImplementedError # このメソッドはオーバーライドする想定(インターフェースのみ定義するなど)
  end
end

class ClassA < HelloClass
  include HogeExtension
  include FugaExtension

  def hello
    'ClassA world'
  end
end

共通する振る舞いをまとめる

include HogeExtensionしたクラスのインスタンスの振る舞いのテストをこんな感じでshared_examples_forでまとめてみます。

ブロック引数objとしてインスタンスを受け取るようにしておきました。

# spec/shared/examples/hoge_extension.rb
shared_examples_for 'HogeExtension' do |obj|
  describe '#hoge' do
    subject { obj.hoge }
    it "returns 'hoge'" do
      is_expected.to eq('hoge')
    end
  end
end

同様にFugaExtension

# spec/shared/examples/fuga_extension.rb
shared_examples_for 'FugaExtension' do |obj|
  describe '#fuga' do
    subject { obj.fuga }
    it "returns 'fuga'" do
      is_expected.to eq('fuga')
    end
  end
end

HelloClassクラスについては、インスタンスの生成方法がわかっているということにして、described_classを使いつつ、#helloが実装されていること、Stringを返すことをテストしてみました。

# spec/shared/examples/hello_class.rb
shared_examples_for 'HelloClass' do
  it { expect(described_class.ancestors).to be_include(HelloClass) }
  describe '#hello' do
    subject { described_class.new.hello }
    it { expect { is_expected }.to_not raise_error(NotImplementedError) }
    it { is_expected.to be_an_instance_of(String) }
  end
end
4
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?