Mock、Stub勉強会(ruby) 瀬尾直利 2011/05/20
Mock、Stub勉強会(ruby)
瀬尾直利
2011/05/20
アウトライン
�MockとStubとは
�MockとStubの使い分け
�Mock Frameworkの構文比較
�More about RR (Double Ruby)
MockとStubとは
� Test Doubleという概念の一部。
� Double とは、代役のことで、テスト用にオブジェクトを入れ替えるときに一般的に用いられる言葉。
[1] xUnit Test Patterns by Gerard Meszaros
http://xunitpatterns.com/Test%20Double.html
Test Doubleの種類
� DummyDummyDummyDummy�受け渡されることはあるが実際に使用されることはない。パラメータリストを埋めたいだけといった場合に利用されることが多い。
� FakeFakeFakeFake�実際に動作するように実装されてはいるが、手抜きされており製品版には向かない。
� StubStubStubStub� テスト時の呼び出しに対してあらかじめ決められた値を返すもの。
� SpySpySpySpy�呼び出しに基づく情報を記録するスタブ。例えば、何通メールが送られたかだけカウントするメールサービスなどが該当する。
� MockMockMockMock�テスト時の呼び出しの仕様を表したもの。期待されない呼び出しが行なわれた場合は例外をスローできる。期待された呼び出しがすべて行われたか確認できる。
Mock サンプルコード on Mocha (1)
# expect a class method
Duck.expects(:quack)
Duck.quack
# => verify success
# expect an instance method of a real object
duck = Duck.new
duck.expects(:quack)
# => verify success
# traditional mocking
duck = mock(’duck’)
duck.expects(:quack)
duck.quack
# => verify success
Mock サンプルコード on Mocha (2)
# expect multiple invocations
duck = mock(’duck’)
duck.expects(:quack).times(3)
duck.quack
duck.quack
duck.quack
# => verify success
# specifying parameters and specifying a return value
duck = mock('duck')
duck.expects(:waddle).with(2).returns(3)
duck.waddle(2) # 3
# => verify success
duck.waddle(1) # 3
# => verify failure
Stub サンプルコード on Mocha
# return values and exceptions
duck = Duck.new # = mock('duck')
duck.stubs(:flag).returns(true,false).then.raises(BrokenWingError)
duck.flap # => true
duck.flap # => false
duck.flap # => raises BrokenWingError
※ ここでは返り値のテストはしていない。
イメージ付きました?
アウトライン
�MockとStubとは
�MockとStubの使い分け
�Mock Frameworkの構文比較
�More about RR (Double Ruby)
MockとStubの使い分け
�Mock は呼び出しの verification をする。
�Stub はそれをしない。
�→ で、結局どう使い分ける?
テストの観点の違い[3]
�相互作用(振る舞い)中心のテスト → Mock。
�状態中心のテスト → Stub。
相互作用中心のテスト
� テスト対象のシステムと外部のコンポーネントとの間で正しいやり取りがされるかのテスト。
� いわばプロトコルのテスト。
� 外部のコンポーネントを Mock で置き換え、システムが正しい呼び出しをしているかを監視する。
� 例えば MVC でいう Controller の単体テスト。
� Modelが正しく実装された時にControllerが正しく動作することが、Modelがなくても保証される。
状態中心のテスト
� テスト対象のシステムが正しい結果を返すかというテスト。
� 最終的に返ってくる結果だけが重要。
� 外部のシステムとの統合が面倒な時に Stub を利用してテストを簡単にする。
� 入出力の例を Stub を使って記述する。
サンプルコード) 振る舞い中心のテスト
left_tire = mock('left_tire')
right_tire = mock('right_tire')
robot = Robot.new(left_tire, right_tire)
left_tire.expects(:move).with(-5)
right_tire.expects(:move).with(+5)
robot.turn_left
# behaviour verification
left_tire.verify
right_tire.verify
サンプルコード) 状態中心のテスト
left_tire = mock('left_tire').stub(:move).returns(-5);
right_tire = mock('right_tire').stub(:move).returns(+5);
robot = Robot.new(left_tire, right_tire)
initial_position = robot.position
Initial_direction = robot.direction
robot.turn_left
# state verification
assert_equal initial_position, robot.position
assert robot.direction > initial_direction
ここまで
おk?
アウトライン
�Test Double
�MockとStubの違い
�Mock Frameworkの構文比較
�More about RR
Mock Frameworkの構文比較
�RR
�Mocha
�Rspec/mocks
�Flexmock
の構文を比較してみた[4]。
Mock 期待: Userクラスのfindメソッドに引数'99'が渡され、変数userが返される。
mock(User).find('99') { user }
User.expects(:find).with('99').returns(user)
User.should_receive(:find).with('99').and_return(user)
flexstub(User).should_receive(:find).with('99').and_return(user).once
RR
Mocha
Spec/mocks
Flexmock
Stub (1)期待: Userクラスのfindメソッドに引数‘99’が渡された場合、user1が、それ以外の場合 user2 が返される。
stub(User).find('99') { user1 }
stub(User).find { user2 }
User.stubs(:find).with(anything).returns(user2)
User.stubs(:find).with('99').returns(user1)
RR
Mocha
Stub (2)期待: Userクラスのfindメソッドに引数‘99’が渡された場合、user1が、それ以外の場合 user2 が返される。
users = {
'99' => user1,
'default' => user2
}
User.stub!(:find).and_return do |id|
users[id] || users['default']
end
users = {
'99' => user1,
'default' => user2
}
flexstub(User).should_receive(:find).and_return do |id|
users[id] || users['default']
end
Spec/mocks
Flexmock
所感
�可読性はMochaやrspec
�実際のコードに近いのはRR
�短くかけるのはRR
どう?
アウトライン
�MockとStubとは
�MockとStubの使い分け
�Mock Frameworkの構文比較
�More about RR (Double Ruby)
RRの使い方[6]
class Test::Unit::TestCase
include RR::Adapters::TestUnit
end
Spec::Runner.configure do |config|
config.mock_with :rr
# もしくは、バージョンの非互換性で動作しない場合は# config.mock_with RR::Adapters::Rspec
end
require 'rr'
extend RR::Adapters::RRMethods
object = Object.new
mock(object).method_name {:return_value}
object.method_name # :return_valueが返りますRR.verify # ダブルの期待を満たしているかを検証します
test/unit
rspec
単独使用単独使用単独使用単独使用
RRのメソッド
� mock もしくは mock!
� stub もしくは stub!
� dont_allow もしくは dont_allow!
� proxy もしくは proxy!
� instance_of もしくは instance_of!
! が付いているメソッドは純粋なダブルオブジェクトを作成します。
obj = MyObject.new
mock(obj).hello # 上書き。Partial Mocking
obj = mock! # Pure mock object. Traditional Mocking
obj.hello
Mock サンプルコード on RR
view = View.new
mock(view).render(:partial => "user_info") {"Information"}
mock(view).render.with_any_args.twice do |*args|
if args.first == {:partial => "user_info}
"User Info"
else
"Stuff in the view #{args.inspect}"
end
end
次次次次のののの方法方法方法方法でモックへでモックへでモックへでモックへ渡渡渡渡されるされるされるされる引数引数引数引数をいくつでもをいくつでもをいくつでもをいくつでも許許許許せますせますせますせます。。。。
Q. どういうどういうどういうどういう意味意味意味意味でしょうかでしょうかでしょうかでしょうか????
Stub サンプルコード on RR
jane = User.new('Jane')
bob = User.new('Bob')
stub(User).find('42') {jane}
stub(User).find('99') {bob}
stub(User).find do |id|
raise "Unexpected id #{id.inspect} passed to me"
end
Q. どういうどういうどういうどういう意味意味意味意味でしょうかでしょうかでしょうかでしょうか????
dont_allow サンプルコード
dont_allow(User).find('42')
User.find('42') # TimesCalledError例外が送出されます
決決決決してしてしてして呼呼呼呼ばれないばれないばれないばれない期待期待期待期待。。。。呼呼呼呼びびびび出出出出されるとされるとされるとされると例外例外例外例外がががが発生発生発生発生。。。。
mock.proxy サンプルコード
class Test
def method_a
“a”
end
end
test = Test.new
mock.proxy(test).method_a # expects once called
puts test.method_a # a
返返返返りりりり値値値値をををを置置置置きききき換換換換えないえないえないえない mock
# normal mock
mock(test).method_a # expects once called
puts test.method_a # 空
実際実際実際実際のののの処理処理処理処理をさせをさせをさせをさせ、、、、返返返返りりりり値値値値をををを受受受受けけけけ取取取取りりりり、、、、偽装偽装偽装偽装してしてしてして返返返返すすすす。。。。
view = controller.template
mock.proxy(view).render(:partial => "user_info") do |html|
html.should include("John Doe")
"Different html"
end
stub.proxy サンプルコード
view = controller.template
stub.proxy(view).render(:partial => "user_info") do |html|
html.should include("Joe Smith")
html
end
返返返返りりりり値値値値をインターセプトをインターセプトをインターセプトをインターセプト
instance_of サンプルコード
mock.instance_of(User).valid? { false }
あるクラスのあるクラスのあるクラスのあるクラスの全全全全てのインスタンスをてのインスタンスをてのインスタンスをてのインスタンスをStubするするするする。。。。
※ Mocha だと any_instance_ofなのでもう少しわかりやすい・・・
Spy サンプルコード
メソッドメソッドメソッドメソッド呼呼呼呼びびびび出出出出しのしのしのしの記録記録記録記録
subject = Object.new
stub(subject).foo
subject.foo(1)
subject.should have_received.foo(1)
subject.should have_received.bar # this fails
rspec
Double graphs
例例例例) 期待期待期待期待: #foo メソッドがメソッドがメソッドがメソッドが呼呼呼呼びびびび出出出出されされされされ、、、、そのそのそのその返返返返りりりり値値値値にににに対対対対してしてしてして、、、、#bar メソッドがメソッドがメソッドがメソッドが呼呼呼呼びびびび出出出出されるされるされるされる。。。。
object = Object.new
stub(object).foo.stub!.bar{:baz}
object.foo.bar # success and returns :baz
ワイルドカードanything
mock(object).foobar(1, anything)
object.foobar(1, :my_symbol)
is_a
mock(object).foobar(is_a(Time))
object.foobar(Time.now)
numericmock(object).foobar(numeric)
object.foobar(99)
booleanmock(object).foobar(boolean)
object.foobar(false)
duck_typemock(object)foobar(duck_type(:walk, :talk))
arg = Object.new
def arg.walk; ‘walk’; end
def arg.talk; ‘talk’; end
object.foobar(arg) # expects arg has methods, walk and talk.
ワイルドカードRanges
mock(object).foobar(1..10)
object.foobar(5)
Regexps
mock(object).foobar(/on/)
object.foobar(“ruby on rails”)
hash_including
mock(object).foobar(hash_including(:blue => "#0000FF“, :red => "#FF0000"))
object.foobar({:red => "#FF0000", :blue => "#0000FF", :green => "#00FF00"})
satify
mock(object).foobar(satisfy {|arg| arg.length == 2})
object.foobar("xy")
any_times
mock(object).method_name(anything).times(any_times) {return_value}
最後に
実際のコード
を見てみよう!
参考文献
� [1] Test Double at XUnitPatterns.comhttp://xunitpatterns.com/Test%20Double.html
� [2] テストダブルhttp://capsctrl.que.jp/kdmsnr/wiki/bliki/?TestDouble
� [3] ricollab Web Tech Blog » Blog Archive » Mock と Stub についてhttp://blogs.ricollab.jp/webtech/2009/09/mock_and_stub/
� [4] brian - Introducing RRhttp://pivotallabs.com/users/brian/blog/articles/352-introducing-rr
� [5] An Introduction to Mock Objects in Ruby by James Mead http://jamesmead.org/talks/2007-07-09-introduction-to-mock-objects-in-ruby-at-lrug/
� [6] RR README.dochttps://gist.github.com/330284/fb3fdbf2dd91a800ed83de37b8d15ea84c3dd2c8