autotest の導入方法
前々から使いたかったautotest を導入したので手順を記録します。OS はMac OS X(10.5.5), ruby は1.8.7です。
なお、参考にしたのは「まるごと Ruby! Vol.1(p.124~p.127)」です。
やること
- autotest のインストール
- aiutotest を使ってみる
- autotest のカスタマイズ
- Growl での通知
- screen での通知
autotest とは
autotest とは、ruby で使える自動テストツールです。次のような特徴を持ってます。
- ファイル監視
ファイルを監視してくれて、ファイルが変更されたら自動的にテストを走らせてくれます。自分でrake〜とか、ruby〜とか打ってテストを走らせなくていいってことですね。
- 通知
また、通知という機能も見逃せません。テスト結果をメールで通知したり、Mac ユーザならGrowl を使った通知も簡単にできちゃうという寸法です。
こんな感じ。
つまり、ファイルを編集していれば、勝手にテストが走って勝手に結果も通知してくれるという、まさに世界が変わるツールなのです。
インストール
autotest はZenTest というgem に組み込まれているので、それをインストールします。これだけでおk。
$ sudo gem install ZenTest
使ってみる
ruby のテスティングフレームワークとしては、主に「Test::Unit」と「RSpec」がありますが、autotest はどちらとも連携できます。今回は、ruby が入っていれば使える「Test::Unit」を例に使ってみます。
ファイル作成
以下のようにディレクトリを作成
$ mkdir -p autotest_example/lib
$ mkdir -p autotest_example/test
さらに、テストファイルと、テスト対象となるファイルを作成します。
- autotest_example/test/test_example.rb
require 'example' require 'test/unit' class TestExample < Test::Unit::TestCase def test_x assert_equal 1, Example.new.x end end
- autotest_example/lib/example.rb
class Example def x # 最初はテストが落ちるように何も返さない end end
autotest 実行
これで準備完了。autotest_example ディレクトリで次のコマンドを実行します。
$ autotest
すると、以下のような実行結果がでて、とまります。
/opt/local/bin/ruby -I.:lib:test -rtest/unit -e "%w[test/test_example.rb].each { |f| require f }" | unit_diff -u Loaded suite -e Started F Finished in 0.009735 seconds. 1) Failure: test_x(TestExample) [./test/test_example.rb:6]: --- /var/folders/NV/NVFCfIgDFDOS9YNDHnhFuk+++TI/-Tmp-/expect20080917-736-w0gmhe-0 2008-09-17 00:33:35.000000000 +0900 +++ /var/folders/NV/NVFCfIgDFDOS9YNDHnhFuk+++TI/-Tmp-/butwas20080917-736-1q7stc9-0 2008-09-17 00:33:35.000000000 +0900 @@ -1 +1 @@ -<1> +<nil> 1 tests, 1 assertions, 1 failures, 0 errors
autotest はdiff 形式でテスト結果を表示するようになっています。
Column::autotest の規約について
ここで少し横道にそれて、autotest の規約について軽く触れます。autotest には以下のような規約があります。
- テストファイルはtest ディレクトリ下に置く
- テスト対象ファイルはlib ディレクトリ以下に置く
- テスト対象ファイルを「example.rb」としたとき
- テストファイル名は test_example.rb となる
- テストクラス名は「TestExample」となる
先ほどautotest を実行したとき、最初は以下のようになっていました。
/opt/local/bin/ruby -I.:lib:test -rtest/unit -e "%w[test/test_example.rb].each { |f| require f }"
ここでは最初に、lib ディレクトリ、test ディレクトリをロードパスに加え、さらにtest/unit をrequire しています。
これによって、テストファイル内では「require "../lib/example"」のように書く必要がない。一方、「test/unit」のrequire もしなくていかと思ったら、変更を検知した際に走るテストでは「test/unit」をrequire しないというKY 実装になっているので、テストファイル内でrequire してやる必要がある。
ファイルを修正し、テストを通過させる
先程テストを走らせた際にはこけてとまってしまった。autotest は現在ファイルの変更を監視している状態にあるので、ファイルを変更して保存してみます。
- autotest_example/lib/example.rb
class Example def x 1 end end
すると、保存した瞬間にテストが走るはずです。結果は以下のように表示されます。
/opt/local/bin/ruby -I.:lib:test test/test_example.rb -n "/^(test_x)$/" | unit_diff -u Loaded suite test/test_example Started . Finished in 0.000926 seconds. 1 tests, 1 assertions, 0 failures, 0 errors /opt/local/bin/ruby -I.:lib:test -rtest/unit -e "%w[test/test_example.rb].each { |f| require f }" | unit_diff -u Loaded suite -e Started . Finished in 0.000981 seconds. 1 tests, 1 assertions, 0 failures, 0 errors
ここで一回autotest を終了するため、ターミナルで「^C」を2回打ちます。
Column::autotest の動作
直前でファイルを修正した後に、autotest は2回のテストを実行しました。最初のテストは、変更箇所に対応するテスト、次のテストは全体のテストです。
本によると、一般にautotest の動作順序は次のようになるとのことです。(この動作は、ZenTest/lib/autotest.rb に定義されています)
- 1. テストファイルと実装ファイルを関連づける
- 2. 全てのテストを実行
- 3. 失敗したテストを取り出す
- 4. ファイルの変更を検知し、変更箇所と関連するテスト、及び、前回失敗したテストを実行
- 5. テストが失敗したら3 へ
- 6. テストが成功したら2 へ
- 7. 「^C」が1回押されたらテスト情報をリセットして2 へ
- 8. 「^C」が2回続けて押されたら終了
rails でのautotest
rails でのautotest はRAILS_ROOT でautotest と打てば、あとは規約にそってテストが実行されます。先に述べた規約ではなく、rails の規約でテストが実行されるので、特に何もせず使うことができます。
通知のカスタマイズ
最初に、autotest の魅力に「通知」があると書きました。最後に、その通知のカスタマイズをやってみます。今回は、Growl での通知、screen での通知を試してみます。
雛形ファイルをコピー
以下のコマンドを実行します。
$ cp `gem environment gemdir`/gems/ZenTest-3.10.0/example_dot_autotest.rb ~/.autotest
この雛形ファイルを編集することで、通知方法をカスタマイズすることができます。
Growl との連携
なお、ここでは「autotestのGrowl通知をカスタマイズする - ザリガニが見ていた...。」を参考にしました。
- Growl, growlnotify のインストール
あらかじめ、Growl とgrowlnotify をインストールしておきます。インストールの方法は、以下のblog を参照。
- 通知用画像のダウンロード
テスト成功時、失敗時の通知に使える画像があるのでDL しておきます。
$ mkdir ~/.autotest_image
$ cd ~/.autotest_image
$ curl -O http://blog.internautdesign.com/files/rails_ok.png
$ curl -O http://blog.internautdesign.com/files/rails_fail.png
- .autotest を以下のように編集します
# -*- ruby -*- require 'autotest/growl' module Autotest::Growl IMG_OK = '~/.autotest_image/rails_ok.png' IMG_FAIL = '~/.autotest_image/rails_o.png' class << self undef growl def growl(title, msg, img = nil, pri=0, sticky="") msg += " at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}" img ||= "/Applications/Mail.app/Contents/Resources/Caution.tiff" system "growlnotify -n autotest #{title} -m #{msg.inspect} --image #{img} -p #{pri} #{sticky}" end end Autotest::HOOKS[:red].clear Autotest::HOOKS[:green].clear Autotest::HOOKS[:all_good].clear Autotest.add_hook :ran_command do |at| output = at.results.join failed = output.scan(/^\s+\d+\) (Failure|Error):\n(.*?)\((.*?)\)/) if failed.size == 0 growl "Tests Passed", "Tests passed", IMG_OK, -2 else f,e = failed.partition { |s| s[0] =~ /Failure/ } growl "Tests Failed", "#{f.size} Failures #{e.size} Errors", IMG_FAIL, 2 end end end
さらに、Growl を以下のように設定しました。
- 表示オプション>緊急 を赤
テストの表示結果は以下のようになりました丶(´▽`)ノ
screen との連携
Leopard にはscreen がプリインストールされているので、そのまま使えて素敵です。ここでは、Autotest::Screenが便利な件について。 (SaikyoLine.jp)を参考にしました。
- .autotest
# -*- ruby -*- require 'autotest/screen' Autotest::Screen.statusline = "[%m/%d %02c] %{=r dd}%-w%{=b bw}%n %t%{-}%+w %=" # Autotest::Screen.statusline = %q[%{=r dd} %-w%{=b dd}[%n] %t %{-}%+w %=] class Autotest::Screen Autotest.add_hook :run_command do |at| message 'Testing...' if execute? end Autotest.add_hook :ran_command do |at| if execute? then output = at.results.join failed = output.scan(/^\s+\d+\) (Failure|Error):\n(.*?)\((.*?)\)/) if failed.size == 0 then message "All Green", :green else f,e = failed.partition { |s| s[0] =~ /Failure/ } message "Red F:#{f.size} E:#{e.size}", :red end end end end
表示結果は以下のようになりました。
個人的には、screen との連携の方がよさそうなのでこっちを使うことにします。Growl だとたまに見逃しそうなので。