RubyKaigi 2015の2日目(2015年12月12日)にThe history of testing framework in RubyというタイトルでRubyのテスティングフレームワークの歴史を紹介しました。
関連リンク:
内容
この発表の内容は次の2つにわかれています。
-
Rubyにバンドルされているテスティングフレームワークを中心に、Ruby用のテスティングフレームワークの歴史を紹介
-
Test::Unit APIの特徴を紹介
歴史はRubyのテスティングフレームワークの歴史(2014年版)をベースにそろそろリリースされるRuby 2.3までの情報を追加したものになっています。RubyUnitについてもっと知りたい方はRubyUnitの開発者である助田さんが書いた[Ruby] RubyKaigi 2015(2日目): 遠回りするかなも読むとよいです。
Test::Unit APIについてはスライドの72ページ以降を参照してください。ざっくりというとTest::Unit APIは普通のRubyのコードを書くようにテストを書けることを大事にしている、ということを具体例を交えて説明しています。
ただし、フィクスチャーのAPIはそれほどRubyらしくありません。いつものRubyでは次のようにブロックを使って後処理の詳細を隠蔽できます。
File.open("README") do |file|
# ...
end # ← ブロックが終わったら開いたファイルを確実に閉じる
しかし、Test::Unit APIでは次のように別のメソッドで後処理を書かなければいけません。いつものRubyの書き方をできていないということです。
def setup
@file = File.open("README")
end
def teardown
@file.close
end
この点についてFastly, Travis CI, GitHub 共催 RubyKaigi 前々夜祭で田中 哲さんから指摘がありました。そこで、発表までの間にtest-unit(発表を聞いた人なら区別をつけられるでしょうがgemのやつです)のmasterでは次のように使えるようにしました。
def setup
File.open("README") do |file|
@file = file
yield # ここでtest_readが動く
end # テストが終わったらファイルを閉じる
end
def test_read
assert_equal("XXX", @file.read)
end
ただ、@file = file
の部分がもやっとします。ふだんのRubyのコードではインスタンス変数に設定する使い方をしないからです。Test::Unit APIではインスタンス変数にテストで使うオブジェクトを設定するのは普通なので、テストコードという文脈では普通ですが、ふだんのRubyのコードという文脈では不自然なのです。
別の案として次のようなAPIも検討しましたが、やはりインスタンス変数に設定するところがもやっとします。(この書き方は既存のtest-unitですでに使えますが、非公開のAPIを使うことになるので実験用のコード以外では使わないでください。)
def run_test
File.open("README") do |file|
@file = file
super
end
end
なお、環境変数を設定するような次のケースではsetup
の中でyield
する書き方で違和感はありません。インスタンス変数を使わないからです。
def setup
original_lang = ENV["LANG"]
begin
ENV["LANG"] = "C"
yield
ensure
ENV["LANG"] = original_lang
end
end
File.open
のようにブロックにデータを渡す使い方のときは、次のようにテストの中で実行した方がよいのかもしれません。
def test_read
File.open("README") do |file|
assert_equal("XXX", file.read)
end
end
APIについてなにかアイディアがある方はtest-unit/test-unitのissueにコメントをお願いします。RubyKaigi 2015中には次のフィードバックをもらいました。
-
setup
ではなくaround
など違う名前のAPIにした方がよいのではないか -
そもそもなぜ
setup
なのか。テスト毎にインスタンスを作るならinitialize
でやるのが普通ではないか -
テストメソッド(この例では
test_read
)が引数を受け取るようにしたらよいのではないか(そうすればインスタンス変数に設定する必要はなくなる) -
yield
にオブジェクトを渡したら自動でインスタンス変数に設定するのはどうか
違う名前のAPIはよいかもしれませんが、around
はなじまないのが悩ましいところです。Test::Unitの既存のフィクスチャーのAPIはsetup
/teardown
というように役割を名前にしているのでaround
というように順序を名前にするのはなじまないのです。RSpecのようにbefore
/after
というように順序を名前にしているならなじむのかもしれません。
なお、setup
の中で後処理も実行されることがもやっとするのでsetup
ではない名前がよさそう、ということであれば、そこは心配しなくてもよいです。File.open
のように本処理を名前にしているメソッドがブロック付きで呼ばれたら後処理をするようになることは普通のRubyのコードだからです。setup
も本処理の名前です。
initialize
でやるのはよいかもしれませんが、後処理をするタイミングを作れないのが悩ましいところです。普通のRubyのコードではFile.open {...}
のように処理の範囲を明示しないときはGCのタイミングで後処理が動きます。テストでは他のテストに影響を残さないように、テストが終わったら確実に後処理を実行しておきたいです。
テストメソッドが引数を受け取ると複数のセットアップ処理を指定したときに破綻しそうな点が悩ましいところです。
yield
にオブジェクトを渡すと自動でインスタンス変数を設定するのは可読性が悪くなりそうなところが悩ましいところです。
ちなみに、setup
でyield
しても動くようにする仕組みは次の擬似コードのように実装しています。
def run_test
block_is_called = false
setup do
block_is_called = true
test_read
end
test_read unless block_is_called
end
他の発表
先日RubyKaigi 2015にスピーカー・スポンサーとして参加予定でオススメした通り、咳さんのActor, Thread and meが非常に興味深かったです。
bartender(咳フリークはこの名前を聞くとDivを思い出すでしょう)はRubyWorld Conference 2014で話した同期っぽいAPIの実装と同じようなものでした。いまだに着手していませんが、これをベースに複数の通信を並行して進めるときにそれっぽく書けそうなAPIがあるとよさそうと検討していたのでした。
まとめ
クリアコードがシルバースポンサーとして応援したイベント「RubyKaigi 2015」での発表「The history of testing framework in Ruby」の内容を簡単に紹介しました。また、オススメの咳さんの発表についても少し触れました。
今回、スポンサーとして参加した成果はまだわかりません。配布物としてリーダブルコードワークショップのチラシとGroonga族のステッカーを置いておきました。参加者が帰った後に残った分を回収しましたが、どちらもほぼなくなっていました。興味があった方はぜひお問い合わせください。