よたらぼ
自分の興味の赴くままにIT技術系のネタを取りとめもなくメモっています。
Ruby言語やLinuxのネタが多いです。

January 16, 2006 [おもひで]

[Ruby] test/unitがうまくいかない

というレポートをRuby-GNOME2メーリングリストで受け取った。

調べてみたところ、なるほどなーと思ったのでここにメモっておく。

ここでは簡単なライブラリ"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に優先度つけれたら良いのかもしれないけど...。

本日のツッコミ(全4件) [ツッコミを入れる]
なかだ (January 17, 2006 11:18)

つ testrb

むとう (January 17, 2006 11:39)

すみません、もう少し詳しくお願いしますm(__)m。

なかだ (January 17, 2006 12:36)

testrbというのは、まさにそのTest::Unit::AutoRunner.runを実行するためのスクリプトです。
オプションも渡せますし。

$ testrb -v test.rb
Loaded suite test.rb
Started
test_connected(FooClientTest): .

Finished in 0.001 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

むとう (January 17, 2006 14:05)

なるほど。そういうコマンドがあるんですね。
お恥ずかしながら知りませんでした。
ありがとうございます。


編集