Mocha という Ruby のスタブ/モック用ライブラリを使ってテストを書いてみた。 Ruby もテストもスタブ/モックもよくわかってないので色々間違ってるかもしれないです。
スタブとモック
スタブとモックはちょっと違う物らしい。スタブは関数的というか、状態をもったり内部で複雑なことをしてくれるのを期待せず、決まりきった値を返して欲しいときに使うのかな、という風に理解してる。モックはもうちょっと高機能というか賢くて、メソッドが期待する引数とともに呼ばれたかどうかなど細かい条件つきでオブジェクトの振る舞いを偽装しテストできるようにする、のかなとか思ってるけどだいぶあいまい。
以下長いけど実際にテストコードを書いてみた。たぶんあんまり良い例じゃないです。
Ruby mocha test sample code · GitHub にも置いてあります。
スタブを使ったテスト
# -*- coding: utf-8 -*- require 'test/unit' require 'rubygems' require 'mocha' class Rubyist attr_accessor :boss def say "I love Ruby" end end class Boss attr_accessor :name end class TestRubyist < Test::Unit::TestCase def setup @rubyist = Rubyist.new boss = Boss.new boss.name = "Larry Wall" @rubyist.boss = boss end def test_love_perl? # Larry の前ではおべっかをつかう if @rubyist.boss.name == "Larry Wall" @rubyist.stubs(:say).returns("I love Perl") assert_equal @rubyist.say, "I love Perl" end end end
モックを使ったテスト
# -*- coding: utf-8 -*- require 'test/unit' require 'rubygems' require 'mocha' class Rubyist attr_accessor :boss def say(message = "love Ruby") "I #{message}" end end class Boss attr_accessor :name end class TestRubyist < Test::Unit::TestCase def setup @rubyist = Rubyist.new boss = Boss.new boss.name = "Larry Wall" @rubyist.boss = boss end def test_love_perl? # Larry の前ではおべっかを使う if @rubyist.boss.name == "Larry Wall" @rubyist.expects(:say).with("love Perl").returns("I love Perl") assert_equal @rubyist.say("love Perl"), "I love Perl" # 引数が "love Perl" じゃなかったら fail end end end
外部ライブラリに依存するコードのテスト
実際にはシンプルなスタブ/モックでどうにかなるものだけじゃなくて、もっと複雑なものをテストしたいことが多いと思う。以下のコードは XMLRPC::Client を内部で利用しているクラスのメソッドをテストしている、つもり。
request メソッドは XMLRPC::Client で通信した結果を利用して値を返すが、テストのときは XMLRPC::Client にいちいち通信させたくない(結果が一意とは限らないし)というときに、 XMLRPC::Client のインスタンスと似た振る舞いをもったオブジェクトを偽装して request メソッドの中で利用させている、つもり。
このへんになってくると、 Ruby のコーディング能力やら Mocha への理解度やらテストに対する知見やらがもろもろ不足しているせいで、テストコードが頻繁にエラーになってしまう。あちこちいじってどうにかこうにか実行できるテストを書けている、というていたらく。テストコードがエラーになるって本末転倒な感じで情けない。
そもそもこういう考え方で、テストの方針じたいがあってるのかどうかにも自信がない。ぐぐって見つかるのはどうしても使い方のお手本のようなものが多くて、実践的な例はそう豊富にはない。そういうのはやっぱりオープンソースソフトウェアのテストなどを探してたくさん読んで学んでいくしかないんだろうな。
# -*- coding: utf-8 -*- require 'test/unit' require 'rubygems' require 'mocha' require 'xmlrpc/client' module Sasimi class Blog def self.api 'http://b.hatena.ne.jp/xmlrpc' end def self.request(method, url) client = XMLRPC::Client.new2(self.api) ok, result = client.call2(method, url) "やたー#{result}ブクマいったよー" end end end class TestSasimiBlog < Test::Unit::TestCase def test_request_get_total_count o = mock() o.expects(:call2).with('bookmark.getTotalCount', 'http://d.hatena.ne.jp/a666666/').returns([true, 100*100*100]) XMLRPC::Client.expects(:new2).with('http://b.hatena.ne.jp/xmlrpc').returns(o) assert_equal Sasimi::Blog.request('bookmark.getTotalCount', 'http://d.hatena.ne.jp/a666666/'), "やたー1000000ブクマいったよー" end end # handle response content_type module XMLRPC::ParseContentType def parse_content_type(str) a, *b = str.split(";") a = "text/xml" if a == "application/xml" return a.strip.downcase, *b end end #p Sasimi::Blog.request('bookmark.getTotalCount', 'http://d.hatena.ne.jp/a666666/') #=> [true, 2826]
参考にしたページ
- Mockfight! FlexMock vs. Mocha
- ここのスライドはかなり踏み込んだサンプルコードが載っててとても参考になった
- Mochaが楽しそう - LukeSilvia’s diary
- Time.nowのテスト? それMochaでできるよ - http://rubikitch.com/に移転しました
- Ruby on Rails でのモックとスタブの作成