(※12月の1日から25日まで、日替わりで Ruby の Tips を紹介するイベント、 Ruby Advent Calendar jp: 2009の 10 日目です。 昨日は「えいくん」こと @Sixeight さんでした。 明日は @ohac さんの予定です。)
最初に。Tips と言うか、長すぎてごめんなさい。まとめ力が足りないです。 空気を読まず Rails ネタです。
さて、ふつうの Rails つかいのみなさんは、もう 3.0 とかを先取りアピールしてて、 2-3-stable ってどんなんだっけ?てな感じかと思いますが、現場ではまだまだ 2.3 系も活躍しています。 Ruby と言えばアジャイル開発と言えば Ruby という感じで、コードを書く前にテストを書くなんて当たり前ですが、 今日はモデルを作るだけで 8 個もテストケースが書ける Tips をご紹介します。
$ rails _2.3.4_ 234 $ cd 234 234$ ruby script/generate model TestResult notes:text 234$ rake db:migrate
これだけです。2.3.4 を明示している理由は、後々分かります。 重要なのは、モデルのファイル名が test_*.rb となるようなクラス名を使うことです。 では、コードを書く前にテストを実行してみましょう。 「私は spec の方が……」なんて言わずに黙って rake test:units を実行するのが大人です。
234$ rake test:units (snip) 8 tests, 1 assertions, 0 failures, 0 errors
ほら、もう 8 個もテストケースが書けています。ありきたりな User モデルでは、こうはいきません。 すべてのモデルを test_*.rb にすることによって、最初から 8 倍の生産性です!
……と喜んでいた時代が私にもありました。と言うのも、 先月(2009/11)末にリリースされた 2.3.5 では、この(姑息な)方法が使えなくなったのです。
$ rails _2.3.5_ 235 $ cd 235 235$ ruby script/generate model TestResult notes:text 235$ rake db:migrate 235$ rake test:units (snip) 8 tests, 1 assertions, 0 failures, 1 errors (snip)
ん?エラーが出ているようです。これでは 8 倍の生産性も台無しです。どうしてこうなった?
それではこの辺で、この生産性を叩き出す仕組みを詳しく見ていきましょう。 すでにお気づきかと思いますが、「テストケースが 8 個ある」、と言うだけで、 アサーションは 1 個しかありません。しかもそれは "test the truth" なので、なんのことはない、 どこかからテストケースが降ってきただけのことなのでした。以下のようにすれば確認できます。
235$ ruby -Itest test/unit/test_result_test.rb -v Loaded suite test/unit/test_result_test Started test_results(ActionController::IntegrationTest): . test_results(ActionController::TestCase): . test_results(ActionMailer::TestCase): . test_results(ActionView::TestCase): E test_results(ActiveRecord::TestCase): . test_results(ActiveSupport::TestCase): . test_results(TestResultTest): . test_the_truth(TestResultTest): . Finished in 0.096729 seconds. 1) Error: test_results(ActionView::TestCase): TypeError: wrong argument type Class (expected Module) 8 tests, 1 assertions, 0 failures, 1 errors
見たことあるようなクラス名が並んでいますね。さらに、定義もしていない test_results メソッドが実行されているようです。 そう、いまだに test/unit を愛用している人はお分かりかと思いますが、実はこれ、fixture accessor method なのです。 test/fixtures/test_results.yml などに
235$ cat test/fixtures/test_results.yml one: notes: MyText two: notes: MyText
と書いてあると、test_results(:one) や test_results(:two) で各レコードのインスタンスにアクセスできるというアレです。 これが、例えばモデル名を TestResult にすることによって、fixture accessor method が test_results になり、 テストケースと誤認されるのです。
まとめると、以下のような流れです。
これは、テストスクリプト内でしか使わない fixture accessor method を、 public メソッドとして定義している Rails のバグですね。
2.3.4 ではテストを増やす魔法のモデル名として使えていた Test* ですが、 2.3.5 からは一転して、使っちゃダメなモデル名となってしまいました。 テスト結果を格納するのに使うモデルの名前を TestResult 以外から選べ、というのは酷な話ですね。ヒドいよ!
さて、動作と原因が分かれば後は修正するだけですが、実はかなり前から報告はされていて、 あと一歩でコミットされそうというところまで来ているのですが、なぜか 2.3.5 には取り込まれませんでした。実質 1 行パッチなのに。 みなさんもこんなモデル名で困ったと言うのがあれば +1 の一声とともにチケットに追記してください。
Tips: Testなんちゃらというモデル名は使わない。
これだけではなんなので、対策方法を以下に示してこのエントリを締めたいと思います。
$ cat config/initializers/make_fixture_accessors_private.rb if Rails.env == "test" && %w[2.3.4 2.3.5].include?(Rails.version) require "active_record/fixtures" module ActiveRecord::TestFixtures::ClassMethods unless method_defined?(:setup_fixture_accessors_with_private) def setup_fixture_accessors_with_private(table_names = nil) setup_fixture_accessors_without_private(table_names).each do |table_name| private table_name end end alias_method_chain :setup_fixture_accessors, :private end end end
Merry Christmas and Happy New Year!