Ruby言語やLinuxのネタが多いです。
January 16, 2006
■ ここでは簡単なライブラリ"FooClient"というのを想定する。
このクラスは設計の良し悪しは別として(^^;)、requireした瞬間にFooサーバーに接続し、Rubyプログラムが終了するタイミングで接続を解除する仕様とする。
# fooclient.rb
module FooClient
#require時にServerに接続してしまう
@connected = true
at_exit {
p "at_exit"
#at_exit時に接続を解除
@connected = false
}
module_function
def connected?; @connected; end
end
if $0 == __FILE__
p FooClient.connected?
end
んで、テスト
# test.rb
require 'test/unit'
require 'fooclient'
class FooClientTest < Test::Unit::TestCase
def test_connected
assert_equal true, FooClient.connected?
end
end
んでもって、テストを実行してみると、テストが失敗してしまう(FooClient.connected?がfalseを返す)。FooClientでは確かにat_exitを使ってるのが気持ち悪いことは気持ち悪いけど、でも、テストプログラムのassert_equalの部分はまだat_exitを呼び出す前のような感じがするし・・・。
と、小一時間くらいは悩んでしまったわけだが、理由は簡単で、test/unit.rbが内部で以下のようなコードを持っているのが原因だ。
at_exit do
unless $! || Test::Unit.run?
exit Test::Unit::AutoRunner.run
end
end
これは、test.rbにテストを実行するようなコードを一切書く必要が無いような配慮だ。プログラム終了をトリガーにしてTest::Unit::AutoRunner.runを実行する。
ちなみに、at_exitは以下のようなサンプルを実行するとその動きがわかると思う。
% cat at.rb
at_exit { p 1 }
at_exit { p 2 }
p 3
% ruby at.rb
3
2
1
つまり、at_exitに最後に登録したものから呼ばれることになる(2→1)。
■ さて、上の例の回避策をどうすれば良いかというと、上記のat_exitの順序を意識しつつ、requireの呼び出しをすればよい。おそらくほとんどの場合、require 'test/unit'を最後に呼び出すことになるだろう(つまり、他のライブラリのat_exitを呼び出す*前に*テストを実行する)。
test.rb require 'fooclient' require 'test/unit' : :
■ でもなぁ。requireの順番を気にするなんていう回避策はいただけないよなぁ、と言う場合のもう一つの回避策としては、Test::Unit::AutoRunner.runをtest.rb内で呼び出すという手がある。そうすれば、test/unit.rbのat_exitは関係なくなるからね。
test.rb
require 'test/unit'
require 'fooclient'
class FooClientTest < Test::Unit::TestCase
def test_connected
assert_equal true, FooClient.connected?
end
end
exit Test::Unit::AutoRunner.run
■ まぁ、どっちもどっちか。でも、オレはこっちの方が好きかな。やっぱり、Rubyインタプリタが終了することをトリガーにメインのプログラム(テスト)を実行するってのはちょっとトリッキーすぎな気がする。
■ ともかく、自分が開発していないライブラリを混ぜてテストするときはここであげた点を意識しておいた方がいいだろうね。
at_exitに優先度つけれたら良いのかもしれないけど...。

▲ たけうち [煮詰まってますな。本業? 忙しいなんて逆にうらやましいかも。うちなんて…(以下略)]
▲ むとぽん [う、す、するどい....(-o-;)。 つい先日まではヒマヒマ星人だったのですが....。]