プライベートメソッドをパブリックインターフェースを使ってテストする方法
Jay Fields' Thoughts: Using Stubs to Capture Test Essence
たとえばこんなコードがある。
class MessageGateway def process(message_text) response = post(create_request(message_text)) parse_response(response) end private def post(message) # ... end def create_request(message_text) # ... end def parse_response(response) # ... end end
MessageGateway#processは3つのプライベートメソッドを呼んでいる…create_request, post, parse_response。
プライベートメソッドをテストするなら、__send__を使う方法がある。__send__はRuby 1.9でもprivate methodを呼ぶのに使える。
他にもMochaによるスタブとモックを使う方法もある。この方法は、スタブで偽装して、モックで引数をチェックできるから、結果的にプライベートメソッドのテストができるということだ。この方法ならばプライベートメソッド名を変更してもテストが壊れないという利点がある。
一見、何をテストしているのかわかりにくい気がするが、それは俺の修行不足なんだろうなぁ…なんせテスト対象のメソッド名が出ておらんし。プライベートだからメソッド名なんてどうでもいいってことか。
よくプライベートメソッドもテストすべきかで議論になっていたけど、スタブとモックによる方法でプライベートメソッドもテストできるから、いちおう議論に決着がついたのかな。
ついでにexpectationsによるテストも書いておいた。
class MessageGateway def process(message_text) response = post(create_request(message_text)) parse_response(response) end def post(message) "<status>success</status>" end def create_request(message_text) "<text>#{message_text}</text>" end def parse_response(response) true end end # !> method redefined; discarding old expects ########################################################################### # Test::Unitによるテスト # ########################################################################### require 'rubygems' require 'mocha' require 'test/unit' class TestMessageGateWay < Test::Unit::TestCase # __with_mochaはスタブを使った振舞いベースなテスト # __with_sendは__send__を使った状態ベースなテスト def test_create_request__with_mocha gateway = MessageGateway.new gateway.expects(:post).with("<text>hello world</text>") gateway.stubs(:parse_response) gateway.process("hello world") end def test_create_request__with_send gateway = MessageGateway.new actual = gateway.__send__(:create_request, "hello world") assert_equal "<text>hello world</text>", actual end ################ def test_post__with_mocha gateway = MessageGateway.new gateway.stubs(:create_request).returns("<text>hello world</text>") gateway.expects(:parse_response).with("<status>success</status>") gateway.process("") end def test_post__with_send gateway = MessageGateway.new actual = gateway.__send__(:post, "<text>hello world</text>") assert_equal "<status>success</status>", actual end ################ def test_parse_response__with_mocha gateway = MessageGateway.new gateway.stubs(:create_request) gateway.stubs(:post).returns("<status>success</status>") assert_equal true, gateway.process("") end def test_parse_response__with_send gateway = MessageGateway.new actual = gateway.__send__(:parse_response, "<text>hello world</text>") assert_equal true, actual end end ########################################################################### # expectationsによるテスト # ########################################################################### require 'expectations' Expectations do # test for create_request expect MessageGateway.new.to.receive(:post).with("<text>hello world</text>") do |gateway| gateway.stubs(:parse_response) gateway.process("hello world") end # test for post expect MessageGateway.new.to.receive(:parse_response).with("<status>success</status>") do |gateway| gateway.stubs(:create_request).returns("<text>hello world</text>") gateway.process("") end # test for parse_response expect true do gateway = MessageGateway.new gateway.stubs(:create_request) gateway.stubs(:post).returns("<status>success</status>") gateway.process("") end end # >> Expectations ... # >> Finished in 0.00264 seconds # >> # >> Success: 3 fulfilled # >> Loaded suite - # >> Started # >> ...... # >> Finished in 0.003555672 seconds. # >> # >> 6 tests, 6 assertions, 0 failures, 0 errors