Top Banner
Mock、Stub勉強会(ruby) 瀬尾直利 2011/05/20
36

Ruby test double

May 09, 2015

Download

Documents

Naotoshi Seo
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Ruby test double

Mock、Stub勉強会(ruby)

瀬尾直利

2011/05/20

Page 2: Ruby test double

アウトライン

�MockとStubとは

�MockとStubの使い分け

�Mock Frameworkの構文比較

�More about RR (Double Ruby)

Page 3: Ruby test double

MockとStubとは

� Test Doubleという概念の一部。

� Double とは、代役のことで、テスト用にオブジェクトを入れ替えるときに一般的に用いられる言葉。

[1] xUnit Test Patterns by Gerard Meszaros

http://xunitpatterns.com/Test%20Double.html

Page 4: Ruby test double

Test Doubleの種類

� DummyDummyDummyDummy�受け渡されることはあるが実際に使用されることはない。パラメータリストを埋めたいだけといった場合に利用されることが多い。

� FakeFakeFakeFake�実際に動作するように実装されてはいるが、手抜きされており製品版には向かない。

� StubStubStubStub� テスト時の呼び出しに対してあらかじめ決められた値を返すもの。

� SpySpySpySpy�呼び出しに基づく情報を記録するスタブ。例えば、何通メールが送られたかだけカウントするメールサービスなどが該当する。

� MockMockMockMock�テスト時の呼び出しの仕様を表したもの。期待されない呼び出しが行なわれた場合は例外をスローできる。期待された呼び出しがすべて行われたか確認できる。

Page 5: Ruby test double

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

Page 6: Ruby test double

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

Page 7: Ruby test double

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

※ ここでは返り値のテストはしていない。

Page 8: Ruby test double

イメージ付きました?

Page 9: Ruby test double

アウトライン

�MockとStubとは

�MockとStubの使い分け

�Mock Frameworkの構文比較

�More about RR (Double Ruby)

Page 10: Ruby test double

MockとStubの使い分け

�Mock は呼び出しの verification をする。

�Stub はそれをしない。

�→ で、結局どう使い分ける?

テストの観点の違い[3]

�相互作用(振る舞い)中心のテスト → Mock。

�状態中心のテスト → Stub。

Page 11: Ruby test double

相互作用中心のテスト

� テスト対象のシステムと外部のコンポーネントとの間で正しいやり取りがされるかのテスト。

� いわばプロトコルのテスト。

� 外部のコンポーネントを Mock で置き換え、システムが正しい呼び出しをしているかを監視する。

� 例えば MVC でいう Controller の単体テスト。

� Modelが正しく実装された時にControllerが正しく動作することが、Modelがなくても保証される。

Page 12: Ruby test double

状態中心のテスト

� テスト対象のシステムが正しい結果を返すかというテスト。

� 最終的に返ってくる結果だけが重要。

� 外部のシステムとの統合が面倒な時に Stub を利用してテストを簡単にする。

� 入出力の例を Stub を使って記述する。

Page 13: Ruby test double

サンプルコード) 振る舞い中心のテスト

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

Page 14: Ruby test double

サンプルコード) 状態中心のテスト

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

Page 15: Ruby test double

ここまで

おk?

Page 16: Ruby test double

アウトライン

�Test Double

�MockとStubの違い

�Mock Frameworkの構文比較

�More about RR

Page 17: Ruby test double

Mock Frameworkの構文比較

�RR

�Mocha

�Rspec/mocks

�Flexmock

の構文を比較してみた[4]。

Page 18: Ruby test double

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

Page 19: Ruby test double

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

Page 20: Ruby test double

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

Page 21: Ruby test double

所感

�可読性はMochaやrspec

�実際のコードに近いのはRR

�短くかけるのはRR

どう?

Page 22: Ruby test double

アウトライン

�MockとStubとは

�MockとStubの使い分け

�Mock Frameworkの構文比較

�More about RR (Double Ruby)

Page 23: Ruby test double

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

単独使用単独使用単独使用単独使用

Page 24: Ruby test double

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

Page 25: Ruby test double

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. どういうどういうどういうどういう意味意味意味意味でしょうかでしょうかでしょうかでしょうか????

Page 26: Ruby test double

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. どういうどういうどういうどういう意味意味意味意味でしょうかでしょうかでしょうかでしょうか????

Page 27: Ruby test double

dont_allow サンプルコード

dont_allow(User).find('42')

User.find('42') # TimesCalledError例外が送出されます

決決決決してしてしてして呼呼呼呼ばれないばれないばれないばれない期待期待期待期待。。。。呼呼呼呼びびびび出出出出されるとされるとされるとされると例外例外例外例外がががが発生発生発生発生。。。。

Page 28: Ruby test double

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

Page 29: Ruby test double

stub.proxy サンプルコード

view = controller.template

stub.proxy(view).render(:partial => "user_info") do |html|

html.should include("Joe Smith")

html

end

返返返返りりりり値値値値をインターセプトをインターセプトをインターセプトをインターセプト

Page 30: Ruby test double

instance_of サンプルコード

mock.instance_of(User).valid? { false }

あるクラスのあるクラスのあるクラスのあるクラスの全全全全てのインスタンスをてのインスタンスをてのインスタンスをてのインスタンスをStubするするするする。。。。

※ Mocha だと any_instance_ofなのでもう少しわかりやすい・・・

Page 31: Ruby test double

Spy サンプルコード

メソッドメソッドメソッドメソッド呼呼呼呼びびびび出出出出しのしのしのしの記録記録記録記録

subject = Object.new

stub(subject).foo

subject.foo(1)

subject.should have_received.foo(1)

subject.should have_received.bar # this fails

rspec

Page 32: Ruby test double

Double graphs

例例例例) 期待期待期待期待: #foo メソッドがメソッドがメソッドがメソッドが呼呼呼呼びびびび出出出出されされされされ、、、、そのそのそのその返返返返りりりり値値値値にににに対対対対してしてしてして、、、、#bar メソッドがメソッドがメソッドがメソッドが呼呼呼呼びびびび出出出出されるされるされるされる。。。。

object = Object.new

stub(object).foo.stub!.bar{:baz}

object.foo.bar # success and returns :baz

Page 33: Ruby test double

ワイルドカード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.

Page 34: Ruby test double

ワイルドカード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}

Page 35: Ruby test double

最後に

実際のコード

を見てみよう!

Page 36: Ruby test double

参考文献

� [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