Help us understand the problem. What is going on with this article?

テストの自動実行あれこれ

More than 5 years have passed since last update.

この記事は、Ruby開発環境 Advent Calendar / Jul.の3日目の記事です。

テスト自動実行のススメ

TDDを実践していると、Red -> Green -> Refactoring をリズムよくループさせることが重要となります。
そこで、コードの変更を検出してテストを自動で実行するようなツールによるサポートがあるとものすごく捗ります。

古くは autotest(ZenTest) のような gem を利用して実現していました。
今でも「ruby 自動テスト」とかでググると autotest に関する昔の記事が上位に出たりします。

autotest は定まった環境では非常に便利なのですが、
少し違ったことをしようとすると、変更が非常にめんどくさく、柔軟性に欠けていました(今もそうかは知りません)。

そこで、監視対象とそれが変更された時に何をするかがDSLで簡単に書けるような gem が
autotest に変わって利用されるようになりました。

今回はそのような gem の中から、guardwatchr を紹介します。

guard

guard はデフォルトではカレントディレクトリの Guardfile という設定ファイルにもとづいて監視と実行を行います。

install
% gem install guard
% gem install guard-rspec

今回は guard と guard-rspec をインストールしました。
guard はひな形を利用して、そこから必要に応じて変更して行くのが便利です。

guard-rspec が入っている状態で guard init してみましょう。

init
% guard init
Writing new Guardfile to /Users/okitan/sandbox/guard/Guardfile
rspec guard added to Guardfile, feel free to edit it

Guardfile が作られ、rspec用の設定が追加されたと表示されました。

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(%r{^app/(.+)\.rb$})                           { |m| "spec/#{m[1]}_spec.rb" }
  watch(%r{^app/(.*)(\.erb|\.haml)$})                 { |m| "spec/#{m[1]}#{m[2]}_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{^spec/support/(.+)\.rb$})                  { "spec" }
  watch('config/routes.rb')                           { "spec/routing" }
  watch('app/controllers/application_controller.rb')  { "spec/controllers" }

  # Capybara request specs
  watch(%r{^app/views/(.+)/.*\.(erb|haml)$})          { |m| "spec/requests/#{m[1]}_spec.rb" }

  # Turnip features and steps
  watch(%r{^spec/acceptance/(.+)\.feature$})
  watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$})   { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
end

Guardfile は基本的にguardの引数でどういうことをするかということを指定し、
watch の引数でどのファイルを監視対象とするかを指定し、
watch のブロックで、変更のあったファイルから実行対象への変換ルールを指定します。

例えば、

watch(%r{^lib/(.+)\.rb$})     { |m| "spec/lib/#{m[1]}_spec.rb" }

でしたらlib以下のrubyファイルを監視して、その ruby ファイルに対応する単体テストが指定されます。
ブロックの引数が正規表現のMatchDataですので、m[1]は、正規表現内の()でマッチングしている部分に相当しています。

たとえば、 lib/hoge/fuga.rb が変更にあったら、spec/lib/hoge/fuga_spec.rb が実行されます。

どう実行されるかは、Guard::RSpec#run_on_changes で指定されていて、
ソースを追っていくと rspec コマンドが実行されることがわかります。

guard は DSL によるカスタマイズは簡単なのですが、
新しいことを自分で実装しようとすると、Guard::RSpecのように、
Guard::Guard を継承したクラスを定義しないといけないのが少し敷居が高いです。

とはいえ、guard-で検索するとわかるように、
guard関連のgemは非常に多いので、自分で一から実装することはあまりないかもしれません。

watchr

watchr も基本 guard と同じような DSL なのですが、もう少しシンプルになっています。
watch でマッチパターンを指定するところまでは一緒なのですが、
ブロックでコマンドを実行してしまいます。

watch( 'lib/(.*)\.rb' )      {|md| system("ruby test/test_#{md[1]}.rb") }

実際、ちょっとした自動化をするときは実際これでちょちょっと書くだけで事足りてしまいます。

また、autowatchrを使えばもう少し高度な自動テストのひな形が作れます。

spec/spec.watchr
require 'autowatchr'

Autowatchr.new(self) do |config|
  config.ruby      = 'clear && rspec'
# config.lib_dir   = 'lib'
  config.test_dir  = 'spec'
# config.lib_re    = "^#{config.lib_dir}/.*\.rb$"
  config.test_re   = "^#{config.test_dir}/(.*)_spec\.rb$"
  config.test_file = '%s_spec.rb'
end

こう書いておけば、spec以下のテストファイルの変更の検知だけでなく、
lib以下のファイルの変更も検知し、対応するテストファイルも実行してくれます。
これを基本として、必要に応じて変更していくといいと思います。

guard は Guardfile にいろいろ詰め込んでカオスになりがちですが、
watchr は引数に定義ファイルを指定できますので、
用途に応じてファイルを分けることもでき、管理もシンプルとなっています。

% watchr spec/spec.watchr

まとめ

  • guard は豊富な gem があるので、基本なんでもできる
  • guard で自前で何かやろうとすると少し面倒
  • guard は普段使っていないので嘘書いてるかもしれない
  • watchr はすごくシンプル!

だらだらと色々書きましたが、
必要に応じて使いわけるのもいいかと思います。

okitan
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away