使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」

## はじめに

みなさんこんにちは！
この記事は「必要最小限の努力で最大限実戦で使える知識を提供するRSpec入門記事」、略して「使えるRSpec入門」の第2回です。

今回はRSpecのマッチャについて説明していきます。
第1回と同様、今回も「最低限これだけは」という内容に絞り込んで説明します。
使用頻度の少ないマイナーなマッチャ（注：僕基準）については説明しません。

具体的な項目は以下の通りです。

- マッチャとは何か
- to / not_to / to_not
- eq
- be
- be_xxx
- be_truthy / be_falsey
- change + from / to / by
- 配列 + include
- raise_error
- be_within + of


これからRSpecを始める人はもちろん、何度かRSpecに触れて「うーん、RSpecってわけわからん」となっている人もこの記事で再入門してみると良いかもしれません。

そしてこの記事を最後まで読めば、次のテストコードの意味を理解できるようになっているはずです。

````ruby
expect{ you.read_this_entry }.to change{ you.matcher_expert? }.from(be_falsey).to(be_truthy)
````

それでは始めましょう！

### 対象となる読者

- 「RSpecってなんか怖そう」と思っているRSpec初心者の方
- 「何度か使ってみたけど、RSpecってようわからん」と思っているRSpec経験者の方
- Ruby関係のプロジェクトに放り込まれ、「RSpecでテストを書け」と言われて困惑している他のテストフレームワーク経験者の方

### 対象となるRSpecとRubyのバージョン

- RSpec 3.1.0
- Ruby 2.1.3

基本的に「素のRubyプログラム」をテストする場面を想定していますが、ときどきRailsの使用を前提としたサンプルコードも登場します。

### 今回説明 "しない" 内容

今回の記事ではRSpecのコア機能として提供される標準マッチャを対象とします。
以下のような内容は今回扱いません。

- モック関連のマッチャ（ `expect(...).to receive(...)` など）
- Rails、Capybara関連のマッチャ（ `expect(...).to have_content(...)` など）
- カスタムマッチャ（自作マッチャ）の作り方

なお、モックに関しては第3回、Capybaraに関しては第4回の記事でそれぞれ説明する予定です。

### 第1回の記事はもう読みましたか？

第1回の記事では「RSpecの基本的な構文や便利な機能を理解する」というテーマでRSpecの基本を説明しました。
今回、「読者のみなさんは第1回の内容は理解できている」という前提で説明していくので、まだ読んでいない方は先に第1回を読んでおいてください。

- [使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612)


## そもそもマッチャって何？

「さっきからマッチャ、マッチャって言ってるけど、マッチャってなんやねん？」と思っている方も多いかもしれません。

マッチャ（matcher）は「期待値と実際の値を比較して、一致した（もしくは一致しなかった）という結果を返すオブジェクト」のことです。

簡単なサンプルコードを使って、マッチャを確認してみましょう。

````ruby
expect(1 + 2).to eq 3
expect([1, 2, 3]).to include 2
````

上のサンプルコードでいうと、 `eq` や `include` がマッチャにあたります。
乱暴に言ってしまえば、「 `expect(...).to xxx` の `to` の直後に出てくるやつ（`xxx` の部分）がマッチャ」です。

マッチャは自分自身に定義されている検証ルールに従って、実際の値（`1 + 2` や `[1, 2, 3]`）と期待値（`3` や `2`）を比較し、ルールに合致しているかどうかを判断します。


## to / not_to / to_not

今さら説明するのもおかしいかもしれませんが、「～であること」を期待する場合は `to` を使います。

````ruby
expect(1 + 2).to eq 3
````

反対に「～ではないこと」を期待する場合は `not_to` もしくは `to_not` を使います。

````ruby
expect(1 + 2).not_to eq 1
# または
expect(1 + 2).to_not eq 1
````

ちなみに、「 `expect(...).to xxx` の `to` の直後に出てくるやつがマッチャ」なので、`to` や `not_to` 自体はマッチャではありません。
`to` や `not_to` はマッチャの実行結果を受け取って、テストをパスさせるか否かを判断するRSpecのメソッドです。

**参考: `not_to` と `to_not` 、どちらを使うべき？**
[RSpecの公式ドキュメント](https://www.relishapp.com/rspec/rspec-expectations/docs)によると、 **「読みやすい方を使えば良い」** と書いてあるだけで、どちらか一方を推奨しているわけではありません。
ネイティブスピーカーであれば文脈によって「読みやすい方」を選択できるのでしょうが、普通の日本人はそのニュアンスを識別するのは難しいと思います。（僕もわかりません）
というわけで、 **「別にどっちでもいい」が正解** になります。

ただ、[RSpecの公式ドキュメント](https://www.relishapp.com/rspec/rspec-expectations/docs)のサンプルコードは `not_to` をよく使っているので、迷ったときはとりあえず `not_to` を使ってみてはどうでしょうか？

## eq

これまた基本中の基本ですが、期待値と実際の値が「等しい」かどうかを検証する場合は `eq` を使います。

````ruby
expect(1 + 2).to eq 3
````

RSpecでテストを書く場合、大半はこの `eq` を使ってテストを書くはずです。

## be

`be` は等号・不等号と組み合わせて、値の大小を検証するときによく使われるマッチャです。
たとえば以下のコードは「1 + 2 が 3 以上であること」を検証しています。

````ruby
expect(1 + 2).to be >= 3
````

### ちょっとややこしい話：be と eq の違い

`be` と `eq` は似たような用途で使うこともできます。
ただし、微妙に動きが異なるので注意が必要です。
この項の話はちょっとややこしいので、初心者の方はさらっと読み流す程度でも構いません。

では本題です。
`be` は等号・不等号なしで次のように書くこともできます。

```ruby
message = 'Hello'
expect([message].first).to be message
```

等号・不等号なしで書いた場合は、2つの値が同一のインスタンスかどうかを検証します。
つまり、`equal?` メソッドの結果が `true` かどうかを検証していることになります。

```ruby
message = 'Hello'
# [message].first.equal? message を検証している
expect([message].first).to be message
```

同じ値でもインスタンスが異なる場合はテストは失敗します。

```ruby
message_1 = 'Hello'
message_2 = 'Hello'
# message_1 と message_2 は異なるインスタンスなのでテストが失敗する
expect([message_1].first).to be message_2
```

一方、`be` の代わりに `eq` を使うと `==` を使って比較するので、先ほどのテストはパスします。

```ruby
message_1 = 'Hello'
message_2 = 'Hello'
# message_1 == message_2 の結果は真になるのでテストはパスする
expect([message_1].first).to eq message_2
```

同一インスタンスかどうかを検証する機会はあまり多くないと思うので、大半のテストケースでは `be` ではなく `eq` を使えばOKでしょう。

ただし、`true` / `false` / `nil` や、整数値、シンボルは特殊で、「同じ値であれば同じインスタンス」になるため、`be` を使ってもテストはパスします（`eq` でもパスします）。

```ruby
# true / false / nil はいつでも同じインスタンス
expect(true).to be true
expect(false).to be false
expect(nil).to be nil

# 整数値やシンボルは、同じ値はいつでも同じインスタンス
expect(1 + 1).to be 2
expect(:foo).to be :foo
```

以上の議論をまとめると次のようになります。

- `expect(A).to be B` は `A.equal?(B)` が真であれば（つまり、同一インスタンスであれば）パスする 
- `expect(A).to eq B` は `A == B` が真であれば（つまり、同値であれば）パスする 
- `true` やシンボルのように、「同じ値は常に同じインスタンス」になる場合は `eq` と `be` を入れ替えても同じ結果になる

少しややこしいルールですが、頭の片隅に置いておくといつか役に立つときがくるかもしれません。

## be_xxx (predicateマッチャ)

RSpecで特徴的なのが、 `empty?` のようにメソッド名が「?」で終わり、戻り値が `true` / `false` になるメソッドを `be_empty` のような形式で検証できることです。

たとえばこんな感じです。

````ruby
expect([]).to be_empty
````

このコードは意味的に次のコードと同じになります。

````ruby
expect([].empty?).to be true
# または
expect([].empty?).to eq true
````

他にも、Railsのmodelにバリデーションエラーが発生していないことを検証する場合には、次のように書くことができます。

````ruby
user = User.new(name: 'Tom', email: 'tom@example.com')
expect(user).to be_valid # user.valid? が true になればパスする
````

`be_xxx` のような predicateマッチャを使う利点は、テストコードが自然な英文っぽく読める点です。
（"expect user to be valid"のように）

一方で、「黒魔術的だし、RSpecの流儀を知らない人がテストコードを読んだときに理解しづらい」と感じる人もいて、結構好き嫌いの分かれる記法でもあります。

とはいえ、それほど複雑なルールではないと思いますし、（英文として）読みやすくなるので、僕個人は積極的に活用していった方が良いと考えています。

**余談："predicate"ってどういう意味？**
ちなみに predicate（発音 ≒ プレディカット）は「述語の」という意味の形容詞です。
なので、"predicate matcher"を（むりやり）訳すと「述語的マッチャ」となります。
確かに、英文法的には「 "to" のうしろに動詞として持ってこれるから "be_empty" は述語になっている」と考えられなくもないです。
・・・が、テストを書くときはそんなことまで考える必要はたぶんないと思います（苦笑）。


## be_truthy / be_falsey 

「?」で終わらないが、戻り値として `true` / `false` を返すメソッドは `be_truthy` / `be_falsey` というマッチャで検証することができます。

たとえば、Railsのmodelで `save` メソッドを呼ぶと、保存に成功した場合は `true` 、失敗した場合は `false` が返ってきます。
なので、`be_truthy` / `be_falsey` を使って次のようなテストが書けます。

````ruby
class User < ActiveRecord::Base
  validates :name, :email, presence: true
end
````

````ruby
# 必須項目が入力されていないので保存できない（結果はfalse）
user = User.new
expect(user.save).to be_falsey 

# 必須項目が入力されているので保存できる（結果はtrue）
user.name = 'Tom'
user.email = 'tom@example.com'
expect(user.save).to be_truthy
````

### be_truthy / be_falsey と be true / be false との違い

真偽値の評価に関するRubyの言語仕様はご存知でしょうか？
Rubyでは「`false` または `nil` であれば偽、それ以外は全て真」と評価します。

`be_truthy` / `be_falsey` を使うと、その仕様にあわせて戻り値の真偽を検証してくれます。
つまり、「trueっぽい値」または「falseっぽい値」かどうかを検証してくれるのが `be_truthy` / `be_falsey` です。

一方、 `be true` / `be false` （または `eq true` / `eq false`） を使うと `true` もしくは `false` であることを厳密に検証するので、それ以外の値を渡されるとテストが失敗します。

言葉で説明するよりも具体的なコードを見てもらった方がわかりやすいかもしれません。

````ruby
# どちらもパスする
expect(1).to be_truthy
expect(nil).to be_falsey

# どちらも失敗する
expect(1).to be true
expect(nil).to be false

# be の代わりに eq を使った場合も同様に失敗する
expect(1).to eq true
expect(nil).to eq false
````

というわけで、RSpecで真偽値を確認するテストを書く場合は、何か特別な事情がない限り `be_truthy` / `be_falsey` を使った方が「Rubyらしい真偽値のテスト」ができます。

## change + from / to / by

これまでに述べてきたマッチャに比べると少し構文が難しくなりますが、個人的に使用頻度が高い `change` マッチャの説明をします。

サンプルコードを見ながら使い方を確認してみましょう。
たとえば、以下のようなテストがあったとします。

````ruby
# popメソッドを呼ぶと配列の要素が減少することをテストする
x = [1, 2, 3]
expect(x.size).to eq 3
x.pop
expect(x.size).to eq 2
````

上のテストは `change` マッチャを使うと次のように書き換えることができます。

````ruby
x = [1, 2, 3]
expect{ x.pop }.to change{ x.size }.from(3).to(2)
````

注意してほしいのは `expect{ x.pop }.to` のように、丸括弧ではなく中括弧を使っている点です。
これはRubyの文法的にはブロックを `expect` に渡しています。
同様に、 `change{ x.size }` の部分でも中括弧を使っているので、ここもブロックを渡しています。

`change` マッチャを使ったテストコードは次のように読んでください。

`expect{ X }.to change{ Y }.from(A).to(B)` ＝ 「X すると Y が A から B に変わることを期待する」

それから、`change` のバリエーションとして `by` を使った書き方もあります。
`by` を使うと「（元の個数はともかく）1個減ること」を検証できます。

````ruby
x = [1, 2, 3]
expect{ x.pop }.to change{ x.size }.by(-1)
````

もちろん、減る場合だけでなく増える場合も検証できます。

````ruby
x = [1, 2, 3]
expect{ x.push(10) }.to change{ x.size }.by(1)
````

### change を使った応用例

`change` マッチャを使う場合は、上で示したコードよりももうちょっと複雑な処理を検証することが多いです。

例として、Railsで「userを削除すると、userが書いたblogも削除されること」を検証してみましょう。

````ruby
class User < ActiveRecord::Base
  # dependent: :destroy を付けたので、userを削除するとblogも削除される
  has_many :blogs, dependent: :destroy
end

class Blog < ActiveRecord::Base
  belongs_to :user
end
````

````ruby
it 'userを削除すると、userが書いたblogも削除されること' do
  user = User.create(name: 'Tom', email: 'tom@example.com')
  # user が blog を書いたことにする
  user.blogs.create(title: 'RSpec必勝法', content: 'あとで書く')

  expect{ user.destroy }.to change{ Blog.count }.by(-1)
end
````

このように `change` マッチャを使うと、「Xに対するある操作が、一見無関係なYに影響を与える」といった検証内容を簡潔に表現することができます。

## ときどき使うマッチャあれこれ

さて、モックやRSpec Railsなどの特殊な用途のマッチャを除けば、ここまでに紹介したマッチャで標準的なテストはほとんどカバーできると思います。

続いてここからは「そこまで使用頻度は高くないけど、知っておくと便利かも」というマッチャをいくつか紹介していきます。

### 配列 + include

`include` マッチャを使うと、「配列に～が含まれていること」を検証することができます。

````ruby
x = [1, 2, 3]
# 1が含まれていることを検証する
expect(x).to include 1
# 1と3が含まれていることを検証する
expect(x).to include 1, 3
````

他にも `include` はハッシュや文字列に対しても使うことができます。
また、 `contain_exactly` というマッチャを使うと、配列の順番は無視して要素の個数や内容を検証することができます。

・・・などなど、バリエーションはいろいろあるのですが、個人的には「配列 + `include`」以外ほとんど使ってないので、ここでは説明を割愛します。
気になる方はRSpecの公式ドキュメントを読んでみてください。

- [\`include\` matcher](https://www.relishapp.com/rspec/rspec-expectations/v/3-1/docs/built-in-matchers/include-matcher)
- [\`contain_exactly\` matcher](https://www.relishapp.com/rspec/rspec-expectations/v/3-1/docs/built-in-matchers/contain-exactly-matcher)

### raise_error

「エラーが起きること」を検証する場合は、 `raise_error` マッチャを使うことができます。

たとえば、「0で除算するとエラーが起きること」は次のようにテストできます。

````ruby
expect{ 1 / 0 }.to raise_error ZeroDivisionError
````

`change` マッチャと同様、`expect` にはブロック（中括弧）を渡している点に注意してください。

続けてもう少し実践的なサンプルコードを見てみましょう。
ここでは自作クラスで明示的にエラーを返すようにしたメソッドをテストしています。

````ruby
class ShoppingCart
  def initialize
    @items = []
  end
  def add(item)
    raise 'Item is nil.' if item.nil?
    @items << item
  end
end
````

````ruby
it 'nilを追加するとエラーが発生すること' do
  cart = ShoppingCart.new
  expect{ cart.add nil }.to raise_error 'Item is nil.'
end
````

上の例では `raise_error` の引数としてエラーメッセージ（文字列）を渡しています。
このように `raise_error` にはエラーのクラスやエラーメッセージ（文字列）など、いろいろな引数を渡すことができます。
詳しい使い方はRSpecの公式ドキュメントを読んでみてください。

- [\`raise_error\` matcher](https://www.relishapp.com/rspec/rspec-expectations/v/3-1/docs/built-in-matchers/raise-error-matcher)

### be_within + of

プログラムのテストというと実行結果がバシッとひとつの値に定まることが多いですが、ときおり「ある程度のゆらぎ」を許容しなければならないケースもあります。
たとえば、ランダムに「あたり」を出すおみくじプログラムなどがそうです。

その場合は `be_within(Y).of(X)` で「数値 X がプラスマイナス Y の範囲内に収まっていること」を検証することができます。

実際に簡易的なおみくじプログラムを作って、テストコードを書いてみましょう。

````ruby
class Lottery
  KUJI = %w(あたり はずれ はずれ はずれ)
  def initialize
    @result = KUJI.sample
  end
  def win?
    @result == 'あたり'
  end
  def self.generate_results(count)
    Array.new(count){ self.new }
  end
end
````

````ruby
it '当選確率が約25%になっていること' do
  results = Lottery.generate_results(10000)
  win_count = results.count(&:win?)
  probability = win_count.to_f / 10000 * 100

  expect(probability).to be_within(1.0).of(25.0)
end
````

テストコードの `probability` が当選確率です。
`KUJI = %w(あたり はずれ はずれ はずれ)` なので、「あたり」を引く確率は 1/4、つまり25%になるはずです。
しかし、「あたり」はランダムに発生する（`sample` メソッドを使っている）ので25%ピッタリになる可能性はほとんどありません。
なので、 `be_within(1.0).of(25.0)` で前後1%のゆらぎを許容しています。
つまり、ここでは `probability` が24から26の間に収まっていればテストはパスします。

**参考：「results.count(&:win?) って何やってるの？」と思った方へ**
`results.count(&:win?)` は `results.count{|r| r.win? }` と同じ意味です。
このあたりのテクニックについてはこちらのQiita記事を参照してください。

- [[初心者向け] RubyやRailsでリファクタリングに使えそうなイディオムとか便利メソッドとか](https://qiita.com/jnchito/items/dedb3b889ab226933ccf)

## まだまだいっぱいあるよ！！（僕はほとんど使わないけど・・・）

RSpecが標準で用意しているマッチャや応用的な使い方はほかにもたくさんあります。
しかし、数年間RSpecでテストを書いてきましたが、僕個人は今回紹介したマッチャ以外はほとんど使ったことがありません。

RSpec上級者の方が読むと、「これもあれも紹介すべきじゃないか」と感じるかもしれませんが、今回は「RSpec初心者の方が、必要最小限の努力で最大限実戦で使える知識を提供する」というスタンスで書いているので、極力紹介する内容を削るようにしました。

とはいえ、自分の引き出しはたくさん持っておくに越したことはありません。

今回紹介したような基本的なマッチャの使い方に慣れてきたら、RSpecの公式ドキュメントを読んでより高度なマッチャの使い方を研究してみてください。

- [RSpec Expectations 3.1](https://www.relishapp.com/rspec/rspec-expectations/v/3-1/docs)

また、RSpec 3から導入されたマッチャ関連の新機能を詳しく知りたい場合は以下のQiita記事が役に立つと思います。

- [今日から使える！RSpec 3で追加された8つの新機能](https://qiita.com/jnchito/items/a4a51852c2c678b57868)

## まとめ

というわけで、今回は「使用頻度の高そうなマッチャ」をピックアップして一通り説明してみました。
これぐらいのマッチャを使いこなせることができれば、普通のテストは問題なく書くことができると思います。

あとは必要に応じて公式ドキュメントを読んで知識を足していったり、ネットを検索して応用的な使い方を調べたりすれば、だんだんと上級者に近づいていくと思います。
最初から「全部覚えなきゃ」と考える必要はないので、気負わずRSpecの世界に飛び込んでみてください。

あ、そういえば冒頭に書いたテストコードは覚えていますか？

````ruby
expect{ you.read_this_entry }.to change{ you.matcher_expert? }.from(be_falsey).to(be_truthy)
````

ここまで読んでくれたみなさんはもう理解できますよね。

「`you.read_this_entry` すれば `you.matcher_expert?` が `false` から `true` になることを期待する」

つまり

**「あなたはこの記事を読めばマッチャを使いこなせるようになっているはず」**

というテストコードを書いていたのでした。
このテストはきっとパスしていると思います！

さて、この入門記事シリーズはもう少し続く予定です。
第3回では「モックの使い方」を、第4回では「CapybaraのDSL」を取り上げるつもりです。

「続きが読みたい！」という方はこの記事をストックしてもらうと、新しい記事を投稿したときに通知メールを送りますので、どうぞよろしくお願いします！


**【2014.11.21 追記】第3回の記事を公開しました！**

- [使えるRSpec入門・その3「ゼロからわかるモックを使ったテストの書き方」](https://qiita.com/jnchito/items/640f17e124ab263a54dd)


**【2015.01.02 追記】第4回の記事を公開しました！**

- [使えるRSpec入門・その4「どんなブラウザ操作も自由自在！逆引きCapybara大辞典」](https://qiita.com/jnchito/items/607f956263c38a5fec24)

### おまけ

せっかくなので、 **「あなたはこの記事を読めばマッチャを使いこなせるようになっているはず」** を実行可能なRubyのクラスとRSpecのテストコードとして表現してみました。

第1回の記事とあわせて読んでくれた方は、きっとテストコードの意味がわかるはずです。

````ruby
class You
  def read_this_entry
    @matcher_expert = true
  end
  def matcher_expert?
    @matcher_expert
  end
end
````

````ruby
RSpec.describe You do
  describe '#read_this_entry' do
    let(:you) { You.new }
    it 'この記事を読むとマッチャを使いこなせるようになっていること' do
      expect{ you.read_this_entry }.to \
        change{ you.matcher_expert? }.from(be_falsey).to(be_truthy)
    end
  end
end
````

ちなみに `from(be_falsey).to(be_truthy)` の部分は、RSpec 3から導入された「コンポーザブルマッチャ」という機能をさりげなく（？）使っています。

「コンポーザブルマッチャ」については以下の記事で詳しく説明しています。

- [今日から使える！RSpec 3で追加された8つの新機能](https://qiita.com/jnchito/items/a4a51852c2c678b57868)


### あわせて読みたい

#### RSpecの公式ドキュメント
今回紹介した内容は、以下の公式ドキュメントを僕なりに要約したような内容になっています。
詳しい仕様を確認したい方は公式ドキュメントを読んでみてください。

[RSpec Expectations 3.1](https://www.relishapp.com/rspec/rspec-expectations/v/3-1/docs)

#### TDDのプロセスを重視したRSpecの入門記事
また、RSpec関連の入門記事としてはこちらの記事もオススメです。

**「RSpecの入門とその一歩先へ ～RSpec 3バージョン～」**

- [第1イテレーション](http://qiita.com/jnchito/items/624f6d5023c279046a1c)
- [第2イテレーション](http://qiita.com/jnchito/items/47f539ddca7dbc01acef)
- [第3イテレーション](http://qiita.com/jnchito/items/b1215a5d548cac4c7de6)

### PR: RSpec 3.1に対応した「Everyday Rails - RSpecによるRailsテスト入門」が発売中です

僕が翻訳者として携わった **「Everyday Rails - RSpecによるRailsテスト入門」** という電子書籍が発売中です。

Railsアプリケーションを開発している方で、実践的なテストの書き方を学んでみたいという人には最適な一冊です。
現行バージョンはRSpec 3.1に対応しています。
一度購入すれば将来無料でアップデート版をダウンロードできるのも本書の特徴の一つです。
よかったらぜひ読んでみてください！

[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)
![Everyday Rails](http://cdn-ak.f.st-hatena.com/images/fotolife/J/JunichiIto/20141024/20141024121511.png)

本書の内容に関する詳しい情報はこちらのブログをどうぞ。

[RSpec 3.1に完全対応！「Everyday Rails - RSpecによるRailsテスト入門」をアップデートしました](https://blog.jnito.com/entry/2014/10/24/120921)