はじめに
RailsアプリでRSpecを自動で走らせるためにGuardを導入したのですが、その設定ファイルであるGuardfile
について理解を深めたかったのでまとめてみました。
踏み込んでいくうちにソースコードまで読むことになり、メタプログラミングの勉強にもなりました。
この理解でguard-rspec
やguard-rubocop
で自動生成される内容にアレンジが加えられるようになったので、生産性が上がりそうです
ベースとして、こちらの記事を参考にさせて頂きました。
rubyのGuardとRSpecをRailsを使わない環境でつかう | 大石制作ブログ
この記事が役に立つ方
- 自動生成されるGuardfileを自分ではアレンジ出来ない方
この記事のメリット
- 自動生成されるGuardfileではどういう設定がされているのかが分かる
- Guardfileを自分用にアレンジ出来るようになる。
環境
- macOS Mojave バージョン10.14.6
- シェル:zsh
- Ruby:2.6.5
- Rails:5.2.2
- RSpec:3.9
- Guard:2.16.1
【準備】Guardのインストールから起動まで
RSpecを使う場合を例にしますので、gemはguard-rspec
にしています。
group :development, :test do
gem 'guard-rspec', require:false
end
Gemfileに上記gemを記載。
↓
$ bundle install
gemをbundlerでインストールする。
↓
$ bundle exec guard init
guardの設定ファイル(Guardfile)を作成。
↓
$ bundle exec guard
guardを起動。
これでGuardfile
に沿って動く状態になります。
次はデフォルトで生成されるGuardfile
を見てみます。
デフォルトのGuardfile
生成されたGuardfile
は以下のようになっています。
guard :rspec, cmd: "bundle exec rspec" do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
# Feel free to open issues for suggestions and improvements
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
# Ruby files
ruby = dsl.ruby
dsl.watch_spec_files_for(ruby.lib_files)
# Rails files
rails = dsl.rails(view_extensions: %w(erb haml slim))
dsl.watch_spec_files_for(rails.app_files)
dsl.watch_spec_files_for(rails.views)
watch(rails.controllers) do |m|
[
rspec.spec.call("routing/#{m[1]}_routing"),
rspec.spec.call("controllers/#{m[1]}_controller"),
rspec.spec.call("acceptance/#{m[1]}")
]
end
# Rails config changes
watch(rails.spec_helper) { rspec.spec_dir }
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
# Capybara features specs
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
# Turnip features and steps
watch(%r{^spec/acceptance/(.+)\.feature$})
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
end
end
見ると、rspecのテストがあるspec
、railsのcontroller
やroutes
などのディレクトリ配下のファイルを監視してくれていて、それに対応するテストを走らせてくれていそうです。
ただ、ほとんど変数なので、具体的にどのディレクトリやファイルを指しているのかは分かりません。
ただ、最初から自動でここまでやってくれるのはありがたいんですが、自分でもアレンジ出来るようになりたかったので中身を読み解いてみます。
Guardfileの基本構文
guard :プラグイン名 オプション do
watch(監視対象) {実行対象}
end
監視対象・実行対象はディレクトリでもファイルでも可です。
{}
内は別の書き方をすると、以下と同義です。
guard :プラグイン名 オプション do
watch(監視対象) do
実行対象
end
end
また、以下のようにすると監視対象
自身が実行対象
になります。
guard :プラグイン名 オプション do
watch(監視対象)
end
監視対象
の中で正規表現キャプチャを使っている場合、ブロック内で変数として代入することが出来ます。(キャプチャについては、以下記事が非常に参考になります)
正規表現で名前付きキャプチャを使う - Qiita
【例】Railsでコントローラーをいじったら自動でテストが走るようにしたい
Railsでコントローラーを変更したとき、対象となるテストを走らせたいという場合を想定すると、Guardfile
には以下のように書きます。
guard :rspec, cmd: "bundle exec rspec" do
watch("%r{app/controllers/(.*?).rb}") {|m| "spec/controllers/#{m[1]}.rb"}
end
もしくは以下のようになります。
guard :rspec, cmd: "bundle exec rspec" do
watch("%r{app/controllers/(.*?).rb}") do |m|
"spec/controllers/#{m[1]}.rb"
end
end
キャプチャされた内容は[0]でなく、[1]から始まることに注意です。
このコードの意味を日本語で書くと、
-
guard-rspec
プラグインを使用して -
app/controllers配下のRubyファイル
を監視して - 動きがあればコマンドラインで
bundl exec rspec
spec/controllers配下のRubyファイル
を実行する
ということですね。
次は自動生成されたGuardfile
を読み解いてみます。
自動生成されたGuardfile
を読み解く
自動生成されるGuardfile
については、メタプログラミングで簡潔に書かれていますが、以下ソースコードを見ると中身が分かるので、理解が可能です。
guard-rspec/dsl.rb at master · guard/guard-rspec · GitHub
以下一文を例にします。
watch(rspec.spec_helper) { rspec.spec_dir }
ここでは
監視対象:rspec.spec_helper
実行対象:rspec.spec_dir
です。
それぞれ変数に代入されているので具体的にどこを指すのかは書かれていませんが、ソースコードを見ると以下のように書かれています。
...略
def rspec
@rspec ||= OpenStruct.new(to_s: "spec").tap do |rspec|
rspec.spec_dir = "spec"
rspec.spec = ->(m) { Dsl.detect_spec_file_for(rspec, m) }
rspec.spec_helper = "#{rspec.spec_dir}/spec_helper.rb"
rspec.spec_files = %r{^#{rspec.spec_dir}/.+_spec\.rb$}
rspec.spec_support = %r{^#{rspec.spec_dir}/support/(.+)\.rb$}
end
end
...略
式展開していくと、監視対象は、
rspec.spec_helper = "spec/spec_helper.rb"
実行対象は、
rspec.spec_dir = "spec"
ということが分かります。
つまり、元の一文
watch(rspec.spec_helper) { rspec.spec_dir }
は、
watch("spec/spec_helper.rb") { "spec" }
に変換することができますね!
おわりに
gemのソースコードをまともに見るのは初めてでしたが、この深堀りの仕方は他にも応用が効くなと感じました。
やっぱりgemとかプラグインを作る方の書くコードはキレイですね
参考にさせて頂いたサイト(いつもありがとうございます)
rubyのGuardとRSpecをRailsを使わない環境でつかう | 大石制作ブログ
正規表現で名前付きキャプチャを使う - Qiita
guard-rspec/dsl.rb at master · guard/guard-rspec · GitHub