Ruby | Guard gem を利用してファイルの変更を検出し、任意のタスクを自動実行する

# Ruby | Guard gem を利用してファイルの変更を検出し、任意のタスクを自動実行する

## 概要
Guard gem を利用してファイルの変更を検出し、任意のタスクを自動実行する。
今回はファイル変更時に RSpec を実行してみます。

## インストール
* guard の RSpec Plugin も一緒にインストールします

~~~bash
$ gem install guard --no-ri --no-doc
$ gem install guard-rspec --no-ri --no-doc
~~~

## 設定ファイル（Guardfile）の生成
RSpec対応のひな形を生成します。

~~~bash
$ guard init rspec
~~~

下記のようなテンプレートが生成されます

~~~ruby:Guardfile
guard :rspec 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|\.slim)$})          { |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 features specs
  watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$})     { |m| "spec/features/#{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
~~~

## 設定ファイルの編集
下記のように編集します。

~~~ruby:Guardfile
guard :rspec do
  watch(%r{^spec/.+_spec\.rb$})
  watch(%r{^lib/(.+)\.rb$})     { |m| "spec/#{m[1]}_spec.rb" }
  watch('spec/spec_helper.rb')  { "spec" }
end
~~~

## guardを起動します

~~~bash
$ guard start
~~~

## プログラムのひな型を生成します

自作 gem の rspec_piccolo を利用します。
rspec_piccoloはプロダクトコードとテストコードのひな形を生成する CLI ツールです。

詳しくは
https://github.com/tbpgr/rspec_piccolo
を参照。

~~~bash
piccolo e Hoge hoge hoge -p
~~~

これで、以下のファイルが生成されます。

~~~ruby:lib/hoge.rb
# encoding: utf-8


class Hoge
  def hoge
    # TODO: implement your code
  end
end
~~~

~~~ruby:spec/hoge_spec.rb
# encoding: utf-8
require "spec_helper"
require "hoge"

describe Hoge do

  context :hoge do
    cases = [
      {
        case_no: 1,
        case_title: "case_title",
        expected: "expected",
      },
    ]

    cases.each do |c|
      it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
        begin
          case_before c

          # -- given --
          hoge = Hoge.new

          # -- when --
          # TODO: implement execute code
          # actual = hoge.hoge

          # -- then --
          # TODO: implement assertion code
          # expect(actual).to eq(c[:expected])
        ensure
          case_after c
        end
      end

      def case_before(c)
        # implement each case before
      end

      def case_after(c)
        # implement each case after
      end
    end
  end
end
~~~

## 自動生成されたファイルを検知して、guardが実行されます

~~~bash
[1] guard(main)>

22:45:17 - INFO - Running: spec/hoge_spec.rb
Run options: include {:focus=>true}

All examples were filtered out; ignoring {:focus=>true}
.

Finished in 0 seconds
1 example, 0 failures
~~~

## テストを実装後、プロダクトコードを保存したguardの実行結果を確認します

~~~ruby:spec/hoge_spec.rb
# encoding: utf-8
require "spec_helper"
require "hoge"

describe Hoge do

  context :hoge do
    cases = [
      {
        case_no: 1,
        case_title: "valid case",
        expected: "hoge",
      },
    ]

    cases.each do |c|
      it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
        begin
          case_before c

          # -- given --
          hoge = Hoge.new

          # -- when --
          actual = hoge.hoge

          # -- then --
          expect(actual).to eq(c[:expected])
        ensure
          case_after c
        end
      end

      def case_before(c)
        # implement each case before
      end

      def case_after(c)
        # implement each case after
      end
    end
  end
end
~~~

~~~bash
[1] guard(main)>

22:47:05 - INFO - Running: spec/hoge_spec.rb
Run options: include {:focus=>true}

All examples were filtered out; ignoring {:focus=>true}
F

Failures:

  1) Hoge hoge |case_no=1|case_title=valid case
     Failure/Error: expect(actual).to eq(c[:expected])

       expected: "hoge"
            got: "# TODO: implement your code"

       (compared using ==)
     # ./spec/hoge_spec.rb:28:in `block (4 levels) in <top (required)>'

Finished in 0.001 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/hoge_spec.rb:17 # Hoge hoge |case_no=1|case_title=valid case
~~~

## プロダクトコードを実行して、テストをパスするようにしてファイルを保存します。

テストが成功することを確認できました

~~~ruby:lib/hoge.rb
# encoding: utf-8


class Hoge
  def hoge
    'hoge'
  end
end
~~~

~~~bash
[1] guard(main)>

22:48:46 - INFO - Running: ./spec/hoge_spec.rb:17
Run options: include {:locations=>{"./spec/hoge_spec.rb"=>[17]}}
.

Finished in 0.001 seconds
1 example, 0 failures
Run options: include {:locations=>{"./spec/hoge_spec.rb"=>[17]}}
.

Finished in 0.001 seconds
1 example, 0 failures
~~~

## 参照
https://github.com/guard/guard
http://rubygems.org/gems/guard

## Guard まとめ記事
http://qiita.com/tbpgr/items/7905535aa0a1d9131688