以前 [ruby] RUBY_PATCHLEVEL の値, [ruby][rr][rspec] mock と stub - HsbtDiary(2010-10-25) を読んで、おれも mock と stub をどう使い分けたらいいのかちゃんと理解できてないなーとずっともやもやしていた。で、今日仕事でテストを書いていたら(えぇ、ちゃんと書いてますよ。まだまだ全然足りないけどね) mock をちょっと上手い具合に使えたかな?という気がしたのでブログに書く。
仕事で書いたコードは ActionMailer のサブクラスに対するテストだったのだけど、それに似せて書いたサンプルコードがこれ。
#!/usr/bin/env ruby require 'test/unit' require 'mocha' class User attr_accessor :user_id, :name, :payment_status def initialize(name) @user_id = self.object_id.abs.to_i @name = name end end class Mailer def deliver_all_payment_notify(users) users.each do |user| if user.payment_status == true deliver_payment_success_notify(user) else deliver_payment_fail_notify(user) end end end private def deliver_payment_success_notify(user) end def deliver_payment_fail_notify(user) end end if $0 == __FILE__ eval(DATA.read) end __END__ class MailerTest < Test::Unit::TestCase def test_deliver_all_payment_notice melody = User.new('Melody') melody.payment_status = true nelson = User.new('Nelson') nelson.payment_status = false mailer = Mailer.new mailer.expects(:deliver_payment_success_notify).once.with(melody) mailer.expects(:deliver_payment_fail_notify).once.with(nelson) mailer.deliver_all_payment_notify([melody, nelson]) end end
Mailer クラスはパブリックメソッドをひとつ、プライベートメソッドをひとつもっていて、実際にメールを送る仕事はプライベートメソッドがやる(という仕様のつもり。空っぽだけど)で、パブリックメソッドにはユーザーのリストを渡して user.payment_status の値によってどういうメールを送るか条件分岐している。この条件分岐が期待したとおりに動くのかをテストしたいわけだ。
そんで上のようなテストを書いた。ここでは mocha というライブラリを利用した。実行環境は ruby 1.9.2p0 (2010-08-18 revision 29036) [i386-darwin9.8.0] で mocha のバージョンは 0.9.10 を使った。
キモは mailer.expects ではじまってる二行で、英語っぽく読むと「mailer は deliver_payment_success_notify というメソッドが melody をともなって一回呼ばれることを期待している」とか。で、テストが走ったときにこのテストケース内で deliver_payment_success_notify メソッドが呼ばれなかったり、二回以上呼ばれたり、引数が melody じゃなかったりすると fail する。
deliver_all_payment_notify メソッドのどこをテストしたいかというと「どのユーザーに対してどのメール配信メソッドが選ばれるのか」なので、それをテストコードとして書くとこんな風になるね、という。まぁあくまで例なので「こんなクソ設計しねーよ普通」とかかもしれませんが。
mocha をしばらく(今年の二月からなのでもうすぐ一年)仕事でテストコード書くときに使ってみた感想としては、テストに失敗したりエラーになったりしたときに何が起こってるのか、慣れるまではわかりづらかった。でも mock を使ったテストがビシッと成功すると、ビートマニア(古い)で難所を息継ぎ無しノーミスで乗り切ったときのような達成感が味わえる。あと、とても強力な道具を手に入れた気分になってモチベーションがあがる。メーリングリストはあまり活発ではないし更新も頻繁ではないけど継続してメンテナンスされていて、 API は安定しているというかむしろ枯れていておそらく今後後方互換性を無くすような大きな変更はないだろうと思うので、安心して利用できるんじゃないかなと思う。まぁふつうは RSpec 使うんでしょうけど・・・。