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


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

という偉大な記事に感動して、僕も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クラスをテスト対象とします。

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)は次のようになります。

# -*- 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で確認できます)

RSpec -fs -c stack_spec.rb

結果はこちら。

Stack
  新規作成の場合
    empty?

Finished in 0.00171 seconds
1 example, 0 failures

🍣 (3) before/after

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

# -*- 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を追加します。

# -*- 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を追加します。

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をカスタムマッチャを使って、次のようにしていきます。

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)は次の通りです。

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にカスタムマッチャを読み込むための記述を追加します。

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を利用できるように、次のように書くこともできます。

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

📚 おすすめの書籍

🖥 サーバについて

このブログでは「Cloud Garage」さんのDev Assist Program(開発者向けインスタンス無償提供制度)でお借りしたサーバで技術検証しています。 Dev Assist Programは、開発者や開発コミュニティ、スタートアップ企業の方が1GBメモリのインスタンス3台を1年間無料で借りれる心強い制度です!(有償でも1,480円/月と格安)