酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

「RSpec をもっと理解したかったので、まとめを作りました」に感動してRuby 1.9.3でやってみた!

[Ruby][Rails] RSpec をもっと理解したかったので、まとめを作りました

fpu_tmp_1354069312.7768_2a8a37

という偉大な記事に感動して、僕もRuby 1.9.3 でトライしてみました。Rspecは2.12.0です!

目次

(1)RSpec はじめの一歩
(2)stackクラスとstackクラスのRspec
(3) before/after
(4) matcher(マッチャ)
(5) カスタムマッチャ
(6) サンプルソース(GitHub)

(1)RSpec はじめの一歩

we use RSpec to #describe Behaviour of a system using
Examples of how #it should work.

「#it がどのように work するか」を例にして、システムの
振る舞い(動作)を説明するためにRSpecを使う

From [Ruby][Rails] RSpec をもっと理解したかったので、まとめを作りました

Rspecの導入の説明には一番マッチします!

(2)stackクラスとstackクラスのRspec

以下のstackクラスをテスト対象とします。

starck.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Stack
  class EmptyError < RuntimeError; end

  def initialize
    @stack = []
  end

  def push(val)
    @stack.push(val)
  end

  def pop
    unless empty?
      @stack.pop
    else
      raise EmptyError
    end
  end

  def empty?
    @stack.empty?
  end

  def size
    @stack.size
  end
end

このstackクラスに対するRSpec(stack_spec.rb)は次のようになります。

stack_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: utf-8 -*-
require 'rubygems'
require 'rspec'
require_relative 'stack'

describe Stack do
  before { @stack = Stack.new }

  context '新規作成の場合' do
    subject { @stack }
    it(:empty?) { should be_true }
  end
end

context は describe のエイリアスで同じ機能です。
このdescribe/contextの一番わかり易い使い分けの説明が、改めて学ぶ RSpecにあります。

* describe はテストする対象をあらわす
* context はテストする時の状況をあらわす

では、このRSpecを実行します。
(rspecコマンド詳細は rspec –helpで確認できます)

コンソール
1
rspec -fs -c stack_spec.rb

結果はこちら。

Stack
  新規作成の場合
    empty?

Finished in 0.00171 seconds
1 example, 0 failures

(3) before/after

次のRSpecを実行するとbefore/afterの流れが分かると思います。

before_after_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding: utf-8 -*-
require 'rubygems'
require 'rspec'

describe 'Level 1' do
  before(:all)  { puts 'L1 - before all'      }
  before(:each) { puts 'L1 - before each' }
  after(:all)   { puts 'L1 - after all'       }
  after(:each)  { puts 'L1 - after each'  }

  it('body-1.1') { puts 'body-1.1' }

  describe 'Level 2' do
    before(:all)  { puts 'L2 - before all'      }
    before(:each) { puts 'L2 - before each' }
    after(:all)   { puts 'L2 - after all'       }
    after(:each)  { puts 'L2 - after each'  }

    it('body-2.1') { puts 'body-2.1'}
    it('body-2.2') { puts 'body-2.2'}
  end

  it('body-1.2') { puts 'body-1.2' }
end

実行結果はインデントを調整して書くと、次のようになります。

L1 - before all
    L1 - before each
      body-1.1
    L1 - after each
    L1 - before each
      body-1.2
    L1 - after each
L1 - after all
L1 - before all
  L2 - before all
    L1 - before each
      L2 - before each
        body-2.1
      L2 - after each
    L1 - after each
    L1 - before each
      L2 - before each
        body-2.2
      L2 - after each
    L1 - after each
  L2 - after all
L1 - after all

(4) matcher(マッチャ)

be_true のようなメソッドを matcher(マッチャ)といいます。 Stack クラスのRSpecに3種類のmatcherを追加します。

starck_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# -*- coding: utf-8 -*-
require 'rubygems'
require 'rspec'
require_relative 'stack'

describe Stack do
  context '新規作成の場合' do
    before { @stack = Stack.new }

    subject { @stack }
    its(:empty?) { should be_true }
  end

  context '空の場合' do
    before { @stack = Stack.new }

    describe '#size' do
      subject { @stack.size }
      it { should == 0 }
    end

    describe '#pop' do
      subject { lambda { @stack.pop } }
      it { should raise_error(Stack::EmptyError) }
    end

    describe '#push' do
      subject { lambda { @stack.push(Object.new) } }
      it { should change(@stack, :size).by(1) }
    end
  end
end

ここで使われているマッチャは次の通りです。

"#size"は「==」
"#pop"は「raise_error」
"#push"は「change」 

結果はこちら。

rspec -fs -c stack_spec.rb 

Stack
  新規作成の場合
    empty?
      should be true
  空の場合
    #size
      should == 0
    #pop
      should raise Stack::EmptyError
    #push
      should change #size

Finished in 0.00388 seconds
4 examples, 0 failures

RSpecの標準マッチャーを一覧にしました。
RSpecの標準matchers(マッチャー)一覧

(5) カスタムマッチャ

カスタムマッチャは、「自分で作成するマッチャ」です。 カスタムマッチャを使うために、Stack クラスにRSpecを追加します。

stack_spec.rb
1
2
3
4
5
6
7
8
9
10
11
describe Stack do
  context 'オブジェクトをpushした場合' do
    before do
      @stack = Stack.new
      @obj = Object.new
      @stack.push(@obj)
    end

    subject { @stack }
    its(:pop) { should == @obj }
  end

この追加したRSpecをカスタムマッチャを使って、次のようにしていきます。

stack_spec.rb
1
2
3
4
5
6
7
describe Stack do
  context 'オブジェクトをpushした場合' do
    before { @stack = Stack.new }

    subject { @stack }
    it { should pop_latest_pushed_value(Object.new) }
  end

カスタムマッチャ”pop_latest_pushed_value”の定義(custom_stack_matchers.rb)は次の通りです。

custom_stack_matchers.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
module CustomStackMatchers
  class PopLatestPushedValue # (1) 専用のクラスを作成
    def initialize(expected)
      @expected = expected
    end

    def matches?(stack) # (2) スペック実行時に呼ばれる。真偽値を返す。
      stack.push(@expected)
      @popped_val = stack.pop

      @popped_val == @expected
    end

    def description
      "pop a object, after push the object"
    end

    def failure_message # (3) should の失敗メッセージ
      <<-MSG
      poped value should equal latest pushed value #{@expected.inspect}, but was #{@popped_val.inspect}
      MSG
    end

    def negative_failure_message # (4) should_not の失敗メッセージ
      <<-MSG
      poped value should not equal latest pushed value #{@expected}, but was #{@popped_val}
      MSG
    end
  end

  def pop_latest_pushed_value(expected)
    PopLatestPushedValue.new(expected)
  end
end

stack_spec.rbにカスタムマッチャを読み込むための記述を追加します。

stack_spec.rb
1
2
3
4
5
6
7
8
9
10
11
require_relative 'custom_stack_matchers'

describe Stack do
  include CustomStackMatchers  # (5) カスタムマッチャを使えるようにする

  contet 'オブジェクトをpush した場合' do
    before { Stack.new }

    subject { stack }
    it { should pop_latest_pushed_value(Object.new) }
  end

ファイル内の複数箇所でCustomStackMatchersを利用できるように、次のように書くこともできます。

stack_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
require_relative 'custom_stack_matchers'

RSpec.configure do |config|
  config.include CustomStackMatchers # (5) カスタムマッチャを使えるようにする
end

describe Stack do
  contet 'オブジェクトをpush した場合' do
    before { Stack.new }

    subject { stack }
    it { should pop_latest_pushed_value(Object.new) }
  end

結果はこちら。

rspec -fs -c stack_spec.rb 

Stack
  新規作成の場合
    empty?
      should be true
  空の場合
    #size
      should == 0
    #pop
      should raise Stack::EmptyError
    #push
      should change #size
  オブジェクトをpushした場合
    should pop a object, after push the object

Finished in 0.00413 seconds
5 examples, 0 failures

(6) ソースコード

今回利用したソースコードをGitHubにアップロードしました。

Source(GitHub)

以上です。

偉大な記事[Ruby][Rails] RSpec をもっと理解したかったので、まとめを作りましたと、今回この記事の掲載に快く許可をいただけたブログ作者の滝沢さんに感謝です!

Thank you very much for your kind notification!

補足:RSpecの書き方、コーディング規約について

RSpecの書き方は、私はRSpecでテストをこんな感じで書いてるを参考にさせていただきました!

Rubyのコーディング規約は、コーディング規約をまとめてみた (Ruby編)を参考にさせていただきました!

補足:モック/スタブについて

RSpecでRailsのテストをしてみるテスト。 | Ginpen.com

RSpec のモックとスタブについては RSpec の Mock と Stub が最初分からなかったけど、理解できたら すごい! という気持ちになった - アントレストラクチャー 滝沢のブログ

関連するサイト

RSpec の入門とその一歩先へ : テストリファクタリングの勉強なります!

テスト環境

テスト環境は以下の通りです。

OS : Mac Lion(OS X 10.7)
Ruby : 1.9.3
Rspec : 2.12.0

おすすめの書籍