社内のプロダクトで一部rspec-givenを使い始めたので、ざっと説明を書いておこうかなと。
追記
もう少し細かく、評価タイミングやこう使ったらいいのではないかという方法をまとめました
ワンライナーなspecを書きやすくするrspec-givenをもっといい感じに使う
rspec-givenとは
Given/When/Then keywords for RSpec Specifications
と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あまり使ってないのでわかりませんが)
例えばこういうスペックが
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}" }
こうなります。
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などを使わないと書けなくなっています。
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で記されてますが、その中でワンライナーで書くとしたらと勧めているのがrspec-givenです。
rspec-givenを使った書き方
以下を例にすると
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...と書きたい時に使えます。
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も非常に強力なフィーチャーだと思います。
Natural Assertionというのは要は==
を使って自然な感じのアサーションを書けるということです。
またfailした時に詳細なレポートが出てくるのも嬉しいです。
例えばrspec標準のrspec-expectationsはこういう書き方になりますが
expect(user.full_name).to eq "#{first_name} #{last_name}")
rspec-givenではこういう書き方を出来るようになっています。
user.full_name == "#{first_name} #{last_name}"
またspecがfailした時にはこんな感じに詳細なレポートがでます。
### 実行結果 ###
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で設定した値がどうだったか等は表示されません。
### 実行結果 ###
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と混成した書き方を示してます