[Rails] RSpecのリファクタリング


Ruby on Rails Tutorialのエッセンスを自分なりに整理してみる7

Railsにおけるリンクの記述方法とそのテスト
http://qiita.com/kidachi_/items/d704e7eb63513c3831ae
の続き。

Ruby on Rails Tutorial（chapter5）
http://railstutorial.jp/chapters/filling-in-the-layout#sec-layout_exercises


##Rspecのリファクタリング

指定のページが指定の要素を持っている（もしくはいない）かをチェックするテストコード。

```spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the h1 'Sample App'" do
      visit root_path
      page.should have_selector('h1', text: 'Sample App')
    end

    it "should have the base title" do
      visit root_path
      page.should have_selector('title',
                        text: "Ruby on Rails Tutorial Sample App")
    end

    it "should not have a custom page title" do
      visit root_path
      page.should_not have_selector('title', text: '| Home')
    end
  end

  describe "About page" do

    it "should have the h1 'About'" do
      visit about_path
      page.should have_selector('h1', text: 'About Us')
    end

    it "should have the title 'About Us'" do
      visit about_path
      page.should have_selector('title',
                    text: "Ruby on Rails Tutorial Sample App | About Us")
    end
  end

  ~
end
```

まず内容理解から。

### describe

テストの塊を表す。例えば

```rb
describe "Static pages" do
  describe "Home page" do
  end
end
```
ならば、「Static pages関連のテストの中のひとつである『Home page』のテスト」。

### it "hoge" do ~ end
「hoge」はあくまでラベル。プログラム的な意味はない。
テスト実行時、失敗箇所を示すものとして表示される。

ブロック（do~end）の中身が実際の各テストのロジックとなる。

```rb
it "should have the h1 'Sample App'" do
  visit root_path
  page.should have_selector('h1', text: 'Sample App')
end
```
上の例では、「root_path(/)にアクセスした際、そのページが
'Sample App'という文字列のh1タグを持っていること」となる。

## 洗練1

visit~、expect(page).to ~など、似たような構文が重複している。
→beforeとsubjectを用いて解決。

<a name="refactor1"></a> 
### before {}

各テストが走る前に毎回実行されるメソッド。
→重複部を before に書くことでテストコードの重複を排除出来る。

###subject {}
ブロック({})内の評価結果が it 内のshouldのレシーバとなる。
本例テストのレシーバ（主語）はどちらもpageになるので、subjectとして括ることが出来る。

### ※page
レスポンスがHTMLである場合、その内容を本メソッドで取得できる。
これはCapybaraの機能のひとつ。


これらを用いてリファクタリングしたテスト。


```spec/requests/static_pages_spec.rb
require 'spec_helper'

subject { page }

  describe "Home page" do
    before { visit root_path } 

    it { should have_selector('h1', text: 'Sample App') }
    it { should have_selector 'title',
                        text: "Ruby on Rails Tutorial Sample App" }
    it { should_not have_selector 'title', text: '| Home' }
  end

  describe "About page" do
    before { visit root_path } 

    it { should have_selector('h1', text: 'About) }
    it { should have_selector 'title',
                        text: "Ruby on Rails Tutorial Sample App | About Us" }
  end

```

## 洗練2
text: "Ruby on Rails Tutorial Sample App"の部分をまとめるため、
helperを用いる

```app/helpers/application_helper.rb
module ApplicationHelper
  # ページごとの完全なタイトルを返却
  def full_title(page_title)
    base_title = "Ruby on Rails Tutorial Sample App"
    if page_title.empty?
      base_title
    else
      "#{base_title} | #{page_title}"
    end
  end
end
```
helperメソッドfull_titleを用いて、もう一段簡潔になった。

```spec/requests/static_pages_spec.rb
require 'spec_helper'

subject { page }

  describe "Home page" do
    before { visit root_path }

    it { should have_selector('h1',    text: 'Sample App') }
    it { should have_selector('title', text: full_title('')) }
    it { should_not have_selector 'title', text: '| Home' }
  end

  describe "About page" do
    before { visit about_path }

    it { should have_selector('h1',    text: 'About') }
    it { should have_selector('title', text: full_title('About Us')) }
  end
```

helperに対するテストも忘れずに。

```spec/helpers/application_helper_spec.rb
require 'spec_helper'

describe ApplicationHelper do

  describe "full_title" do
    it "should include the page title" do
      expect(full_title("foo")).to match(/foo/)
    end

    it "should include the base title" do
      expect(full_title("foo")).to match(/^Ruby on Rails Tutorial Sample App/)
    end

    it "should not include a bar for the home page" do
      expect(full_title("")).not_to match(/\|/)
    end
  end
end
```



## 洗練3

it { should have_selector('h1',    text: 'xxx') }
が各describeで重複している。


→RSpecのShared Exampleとlet（変数生成）を使用してテストの冗長性を排除する。

### Shared Exampleとは

#### shared_examples_for "fuga" do ~ end
で、各describeに共通するテストを記述できる。
その呼び出しは
#### it_should_behave_like "fuga"
で行う。

<a name="refactor3"></a> 
### letとは
テスト内にローカル変数を作成する。
シンボルとブロックを引数に取り、ブロックが返した値を保持するローカル変数となる。
変数の名前はシンボルの値。

```rb
let(:variable)    { 'hoge' }
# 値’hoge’を持つ変数variableが使えるようになる。
```
生成された変数はテスト中すべてのbeforeまたはitブロックで利用できる。

### 共通箇所の定義

Shared Exampleで共通箇所を定義する。
またローカル変数variable1,2を用いる（実際の定義は各describeで行う）。

```rb
describe "Static pages" do
  subject { page }

  shared_examples_for "fuga" do
    it { should have_content(variable1) }
    it { should have_title(full_title(variable2)) }
  end
  ~
```


その上で各describeで呼び出し。

- ローカル変数variableへの値代入（let(:variable) { value }）
- Shared Exampleの呼び出しはit_should_behave_like "fuga"


```rb
  describe "Home page" do
    let(:variable1)    { 'Sample App' }
    let(:variable2) { '' }
    it_should_behave_like "fuga"
  end
```

→describe "Home page" 内で、

-    it { should have_content('Sample App') }
-    it { should have_title(full_title("")) }

が実行される。


### だいぶ簡潔になった


```spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  subject { page }

  shared_examples_for "all static pages" do
    it { should have_content(heading) }
    it { should have_title(full_title(page_title)) }
  end

  describe "Home page" do
    before { visit root_path }
    let(:heading)    { 'Sample App' }
    let(:page_title) { '' }

    it_should_behave_like "all static pages"
    it { should_not have_title('| Home') }
  end

  describe "About page" do
    before { visit about_path }
    let(:heading) { 'About' }
    let(:page_title) { 'About' }

    it_should_behave_like "all static pages"
  end

```

## 以下に続く
[Rails] 基本的なユーザ管理用Modelをテスト駆動で実装してみる
http://qiita.com/kidachi_/items/99f2c90788bd931ea3ee