#264 Guard
- Download:
- source codeProject Files in Zip (260 KB)
- mp4Full Size H.264 Video (17 MB)
- m4vSmaller H.264 Video (10.5 MB)
- webmFull Size VP8 Video (24.8 MB)
- ogvFull Size Theora Video (23.6 MB)
エピソード257[動画を見る, 読む]でRSpecのRequest Specsによるテスト駆動開発でアプリケーションを作成しました。アプリケーションのテストが成功することを、いつでもrake specでチェックできます。
$ rake spec (in /Users/eifion/rails/todo) /Users/eifion/.rvm/rubies/ruby-1.9.2-p0/bin/ruby -S bundle exec rspec ./spec/models/task_spec.rb ./spec/requests/tasks_spec.rb ... Finished in 0.92211 seconds 3 examples, 0 failures
しかし、コードに変更を加えるごとにrake
コマンドを実行しなくてはいけないのが少し面倒です。一連の大きなテストの場合、修正が1、2個のファイルだけだったとしてもすべてのテストが終了するのを待たなくてはいけません。これを自動化できる方法があれば便利でしょう。
この問題を解決するツールはいくつかあります。今回のエピソードではGuardを見ていくことにしましょう。このツールは、ファイルの修正を監視して、修正が行われたらある動作を実行します。多様な環境でGuardを利用できるようにするために多くの機能拡張があります。今回利用するRSpec用では、ファイルの修正を監視して修正があったらspecを実行します。RSpecの代わりにTest::Unitを使用しているのであれば、そのための機能拡張もあります。その他にもCucumber、minitestなど多くの環境向けの機能拡張があります。
インストール
対象のアプリケーションにguard-rspecでGuardを設定し、うまくいくか見ていきましょう。まずGemfile
のtest
グループにgemをいくつか追加します。OS Xで開発している場合、Guardの前提としてrb-fsevent gemをインストールする必要があるので、それを現在の環境がOS Xかどうかのチェックと共に追加します。加えてguard-rspec gemへの参照も追加します。guardそのものは、guard-rspecの依存関係により自動的にインストールされるため、追加する必要はありません。
source 'http://rubygems.org' gem 'rails', '3.0.5' gem 'sqlite3' gem 'nifty-generators' gem 'jquery-rails' group :development, :test do gem 'rspec-rails' gem 'capybara', :git => 'git://github.com/jnicklas/capybara.git' gem 'launchy' gem 'database_cleaner' gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ ↵ /darwin/i gem 'guard-rspec' end
これでbundle
を実行してすべてをインストールします。インストールが終わったら、guard init rspec
を実行してGuardを設定します。
$ guard init rspec Writing new Guardfile to /Users/eifion/rails/todo/Guardfile rspec guard added to Guardfile, feel free to edit it
このコマンドがうまく実行できない場合は、前にbundle exec
をつけてみてください。このコマンドで、Guardfile
が作成されます。今はそのままにしておきますが、後ほど中身を見てみます。
GuardはGrowlの通知機能もサポートしており、growl
gemをインストールすることで有効化できますが、ここでは扱いません。
ではguardコマンドを実行してGuardサーバを起動します。
$ guard Guard is now watching at '/Users/eifion/Desktop/Dropbox/rails/apps_for_asciicasts/ep264/todo' Guard::RSpec is running, with RSpec 2!Running all specs ... Finished in 1.02 seconds 3 examples, 0 failures
問題が起こるようなコードの修正、例えばTask
モデルから検証機能を削除した場合、Guardがすぐに起動してテストが失敗したことがわかります。
class Task < ActiveRecord::Base attr_accessible :name #validates_presence_of :name end
Running: spec/models/task_spec.rb F Failures: 1) Task validates name Failure/Error: Task.new.should have(1).error_on(:name) expected 1 error on :name, got 0 # ./spec/models/task_spec.rb:5:in `block (2 levels) in <top (required)>' Finished in 0.04825 seconds 1 example, 1 failure
検証機能を元に戻すとすぐにまたspecが実行され、今度は成功します。
振る舞いをカスタマイズする
Guardの振る舞いをカスタマイズすることも可能です。デフォルトではビューファイルが修正されたときは起動されません。guard init command
で生成されたGuardfile
を修正することで、この振る舞いを追加することができます。
デフォルトのGuardfileを以下に示します。
# A sample Guardfile # More info at https://github.com/guard/guard#readme guard 'rspec', :version => 2 do watch(%r{^spec/.+_spec\.rb}) watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { "spec" } # Rails example watch('spec/spec_helper.rb') { "spec" } watch('config/routes.rb') { "spec/routing" } watch('app/controllers/application_controller.rb') { "spec/controllers" } watch(%r{^spec/.+_spec\.rb}) watch(%r{^app/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch(%r{^app/controllers/(.+)_(controller)\.rb}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } end
ファイル中の各行はブロック付きのwatch
メソッドの呼び出しで、そこには対応するファイルが修正されたときに実行されるspecが含まれています。例えばroutesファイルが修正されたら、ルーティングのspecを探して修正します。より複雑な条件もあり、その場合は正規表現で記述されています。例えばこの行は/lib
ディレクトリ下のすべてのRubyファイルに一致します。修正されたファイルはカッコ内の正規表現で捉えられてブロックに渡され、一致するspecが実行されます。
watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
デフォルトのGuardfile
では一致するすべてのファイルはRubyファイルです。ビューファイルが変更されたときもspecを実行したいので、これに一致するwatchコマンドを新たに追加します。
watch(%r{^app/views/(.+)/}) { |m| "spec/requests/#{m[1]}_spec.rb" }
この新しいルールでは/app/views
ディレクトリ下のすべてのファイルと一致し、app/views/
の後から最後のスラッシュの前までを取得します。つまり/app/views/tasks/index.html.erb
というファイルの場合、tasks
を取得します。そしてこれをブロックに渡して、対応するrequest specを実行します。
これを確認するために、テストが失敗するような修正をビューファイルに加えてみます。tasks
のindexビューファイルからsubmitボタンを削除すると、Guardがtasksのrequest specを実行し、specが失敗したのがわかります。
Running: spec/requests/tasks_spec.rb .F Failures: 1) Tasks creates a task with validation error Failure/Error: click_button "Add" Capybara::ElementNotFound: no button with value or id or text 'Add' found # ./spec/requests/tasks_spec.rb:13:in `block (2 levels) in <top (required)>' Finished in 0.82203 seconds 2 examples, 1 failure
これを元に戻すと、specはまたすべて成功します。
Guardでブラウザの再読み込みを自動化する
Guardは、アプリケーションの特定のファイルが修正されたときにテストを自動的に実行する優れた方法ですが、他にも便利な使い方があります。例えばCoffeeScriptやSASSのファイルが修正されたときに自動的にコンパイルを実行したり、Gemfile
が修正されたときに自動的にbundle
コマンドを実行したり、初期設定ファイルが修正されたときにPassengerやPowのサーバを再起動したりできます。ここでは、あるファイルが修正されたときにブラウザを自動的に再読み込みさせるためのguard-livereloadというgemを見ていきます。
まずGemfileに参照情報を追加してまたbundleコマンドを実行します。
source 'http://rubygems.org' gem 'rails', '3.0.5' gem 'sqlite3' gem 'nifty-generators' gem 'jquery-rails' group :development, :test do gem 'rspec-rails' gem 'capybara', :git => 'git://github.com/jnicklas/capybara.git' gem 'launchy' gem 'database_cleaner' gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i gem 'guard-rspec' gem 'guard-livereload' end
gemがインストールされたらguard init livereload
を実行してGuardfile
を修正し、その後で再度guard
を起動し直します。
次にChrome、Safari、Firefox用のLiveReload機能拡張をインストールします。LiveReloadのページにインストール用のリンクがあり、ここではSafariを使用しているのでSafari用をインストールします。
機能拡張をインストールしたら、アプリケーションのトップページで右クリックして「Enable LiveReload」を選択して有効化します。
アプリケーションのファイル、例えばスタイルシートを修正したら、ファイルを保存した直後にブラウザのページが再読み込みされます。他のファイルでも同じです。erbファイルでページタイトルを修正したら、ファイルを保存したときにその修正もブラウザに反映されます。
<% title "Todo List" %>
erbファイルを修正したときには少し遅れがありますが、これはLiveReloadがページを読み込む前にspecが再実行されるからです。これにはGuardfile
内の項目の並び順が影響しています。ブラウザの更新を先にした方が、specが実行されている間に新しいページをチェックできるので、より便利でしょう。そのためには、Guardfile
内の2つのguardブロックを入れ替えて、livereload
セクションが先に実行されるようにするだけです。
# A sample Guardfile # More info at https://github.com/guard/guard#readme guard 'livereload' do watch(%r{app/.+\.(erb|haml)}) watch(%r{app/helpers/.+\.rb}) watch(%r{public/.+\.(css|js|html)}) watch(%r{config/locales/.+\.yml}) end guard 'rspec', :version => 2 do watch(%r{^spec/.+_spec\.rb}) watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { "spec" } # Rails example watch('spec/spec_helper.rb') { "spec" } watch('config/routes.rb') { "spec/routing" } watch('app/controllers/application_controller.rb') { "spec/controllers" } watch(%r{^spec/.+_spec\.rb}) watch(%r{^app/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch(%r{^app/controllers/(.+)_(controller)\.rb}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } watch(%r{^app/views/(.+)/}) { |m| "spec/requests/#{m[1]}_spec.rb" } end
これで、ファイルを修正したらすぐに画面で確認できるようになりました。