rspec-givenですっきりしたspecを書く

社内のプロダクトで一部[rspec-given](https://github.com/jimweirich/rspec-given)を使い始めたので、ざっと説明を書いておこうかなと。

__追記__

もう少し細かく、評価タイミングやこう使ったらいいのではないかという方法をまとめました
[ワンライナーなspecを書きやすくするrspec-givenをもっといい感じに使う](http://qiita.com/kakkunpakkun/items/1389ecf503647c9130ba)
# rspec-givenとは

> Given/When/Then keywords for RSpec Specifications

と[rspec-given](https://github.com/jimweirich/rspec-given)のページに書かれている通り、Given, When, Then などを使ってCucumberライクにspecを書けるextensionです。



# rspec-givenを使う理由

すっきりきれいに書ける(と思う)からです。
なんかsubject, let, itはなんかピンと来ない部分がありますが、Given, When, Thenと並ぶと分かりやすいです。

Given, When, Thenの他にもNatural Assertions, Andキーワードなど自然な感じに書けるために色々頑張ってて非常に使いやすいです。

CucumberライクでもあるのでCucumber好きならなお好むところかもしれません。(僕はCucumberあまり使ってないのでわかりませんが)

例えばこういうスペックが

```rb
subject(:user) { User.new(first_name: first_name, last_name: last_name) }
let(:first_name) { 'Yure' }
let(:last_name) { 'Shinatose' }
it { expect(user.full_name).to eq "#{first_name} #{last_name}" }
```

こうなります。

```rb
subject(:user) { User.new(first_name: first_name, last_name: last_name) }
Given(:first_name) { 'Yure' }
Given(:last_name) { 'Shinatose' }
Then { user.full_name == "#{first_name} #{last_name}" }
```


ちなみに、rspec3では`its`が削除されることになっていて、以下の様なワンライナーはrspec-itsなどを使わないと書けなくなっています。

```rb
subject(:user) { User.new(first_name: first_name, last_name: last_name) }
let(:first_name) { 'Yure' }
let(:last_name) { 'Shinatose' }
its(:full_name) { is_expected to eq("#{first_name} #{last_name}") }
```


`its`が削除される理由は[Explanation for why `its` will be removed from rspec-3](https://gist.github.com/myronmarston/4503509)で記されてますが、その中でワンライナーで書くとしたらと勧めているのがrspec-givenです。



# rspec-givenを使った書き方

以下を例にすると

```rb
Given(:user) { User.new(first_name: first_name, last_name: last_name) }
When(:first_name) { 'Yure' }
When(:last_name) { 'Shinatose' }
Then { user.full_name == "#{first_name} #{last_name}" }
```


`Given`がrspecの`subject`に当たるキーワードで、`Then`の中などで値が期待通りかチェックする対象を指定します。ここでは`user`を指定していて、`Then`の中の`user`が呼ばれたタイミングで実行されます。
ちなみに`Given(:user) { ... }`を`Given { ... }`と書くと`Then`毎に実行されます。

`When`は`before`に当たるもので、「◯◯の場合」のような条件を示します。ここでは`first_name`と`last_name`の値を設定していて、それぞれが呼ばれたタイミングで実行されます。

`Then`は`it`に当たり、expectationを書く部分です。この`Then`の中ではNatural Assertionを使ってexpectationに当たるものを書いてます。

また`And`というものもあって、`Then`と似た同じ動きをします。
ただ、Thenで評価されたものを使いまわします。下の例ではThenで評価しているuserを再度評価することはないです。
`Then`を続けて書くよりThen, And, And...と書きたい時に使えます。

```rb
Then { user.first_name == first_name }
And  { user.last_name == last_name }
And  { usre.full_name == "#{first_name} #{last_name}" }
```

# Natural Assertion

Given, When, Thenのキーワードが使えるのが一番のポイントですが、実は[Natural Assertion](https://github.com/jimweirich/rspec-given#natural-assertions)も非常に強力なフィーチャーだと思います。

[Natural Assertion](https://github.com/jimweirich/rspec-given#natural-assertions)というのは要は`==`を使って自然な感じのアサーションを書けるということです。
またfailした時に詳細なレポートが出てくるのも嬉しいです。

例えばrspec標準の[rspec-expectations](https://github.com/rspec/rspec-expectations)はこういう書き方になりますが

```rb
expect(user.full_name).to eq "#{first_name} #{last_name}")
```

rspec-givenではこういう書き方を出来るようになっています。

```rb
user.full_name == "#{first_name} #{last_name}"
```

またspecがfailした時にはこんな感じに詳細なレポートがでます。

```rb
### 実行結果 ###
User
  #full_name
    Then { full_name == "#{first_name} #{last_name}" } (FAILED - 1)

Failures:

  1) User#full_name     Then { full_name == "#{first_name} #{last_name}" }
     Failure/Error: Then { full_name == "#{first_name} #{last_name}" }
       Then expression failed at /path/to/spec/test.rb:24
       expected: nil
       to equal: "Nagate Tanikaze"
         false        <- full_name == "#{first_name} #{last_name}"
         nil          <- full_name
         "Nagate"     <- first_name
         "Tanikaze"   <- last_name
     # ./spec/test.rb:24:in `block in Then'

Finished in 0.00136 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/test.rb:24 # User#full_name     Then { full_name == "#{first_name} #{last_name}" }
```

ちょっと慣れないと読みにくいですが、結果がfalseでfull_nameがnilだと分かり、Whenで渡されたfirst_nameとlast_nameの値も表示されます。

普通にexpectで書くと以下のようにletで設定した値がどうだったか等は表示されません。

```rb
### 実行結果 ###
Failures:

  1) User#full_name should eq "Yure Shinatose"
     Failure/Error: it { expect(user.full_name).to eq "#{first_name} #{last_name}" }

       expected: "Yure Shinatose"
            got: nil

       (compared using ==)
     # ./spec/test.rb:21:in `block (3 levels) in <top (required)>'
```

Natural Assertion周りは他にも`Failure`やfuzzy number matchingなど色んなマッチャーが準備されているので使ってみると面白いです。

# rspec-givenな書き方が出来ない時は

`Invariant`などその他全部の機能を駆使すればほとんどrspec-givenな書き方で書けると思いますが、うまく書き方が浮かばない時もあります。

rspec-givenを使っていても普通にsubject, let, itも使えるし、expectも使えるので深く考えないで普通に今までどおりのrspecの書き方で書いてしまってもいいと思います。

rspec-givenのREADMEの中でも[rspecと混成した書き方](https://github.com/jimweirich/rspec-given#mixing-natural-assertions-and-rspec-assertions)を示してます