Object#method_missingの罠

以下のようなコード*1を書いてDRY原則万歳とか思っていたのだが、

require 'logger'
class Hogelog
  def initialize(file)
    @log = Logger.new(file)
    @log.level = Logger::INFO
  end

  def method_missing(name, message, error = RuntimeError)
    @log.send(name, message)
    if name == "fatal"
      raise error, message
    end
  end
end

Log = Hogelog.new("hoge.log")
...

Rubyのバージョンが1.8.5から1.8.6に上がったら、Test::Unitでassert_raiseしてもHogelog#fatalのRuntimeErrorを捕捉できなくなった。何故だかあなたには分かりますか。

もっとでっかいテストコード群の複数のassertionからHogelog#fatalが呼ばれているのだが、ことごとくassert_raiseの単体テストが通らなくなったわけですよ。

原因は↓ここ。

    if name == "fatal"

nameにはSymbolが渡るので、"fatal"というStringとは決してequalにならない。そこの判定がどうやら1.8.6ではシビアになったらしい。というか1.8.5では緩かったらしい。正しいコードは↓こう。

    if name == :fatal

で、1.8.6ではここでraiseしないのでそのまま処理を続行してしまってだめだめになってしまう予定だったのだが、先に単体テストをかけてみたおかげで危険に気づくことができた。
TDD万歳!

*1:実際にはもっといろんな処理があるので、単純にLoggerを使うわけにはいかなくてHogelogクラスを作った。