使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」

## はじめに

RSpecは難しい、よくわからない、といったコメントをときどき見かけます。
確かにちょっと独特な構文を持っていますし、機能も結構多いので「難しそう」と感じてしまう気持ちもわかります。
（構文については僕も最初見たときに「うげっ、なんか気持ちわるっ」と思った記憶がありますw）

しかし、RSpecに限らずどんなフレームワークでも同じですが、慣れてしまえばスラスラ書けますし、実際僕自身は「RSpecって便利だな－」と思いながらテストコードを書いています。

そこでこの記事では、僕が考える「最低限ここだけを押さえていれば大丈夫！！」なRSpecの構文や、僕が普段よく使う便利な機能をまとめてみます。

具体的には以下のような構文や機能です。

- describe / it / expect の役割
- ネストした describe
- context の使い方
- before の使い方
- let / let! / subject の使い方
- shared_examples の使い方
- shared_context の使い方
- pending と skip の使い分け


あと、できれば今後も次のようなRSpec関連の入門記事を書いていこうと思います。

- よく使うマッチャあれこれ
  - 公開しました！ => [使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5)
- モックの使い方
  - 公開しました！ => [使えるRSpec入門・その3「ゼロからわかるモックを使ったテストの書き方」](https://qiita.com/jnchito/items/640f17e124ab263a54dd)
- よく使うCapybaraのDSL
  - 公開しました！ => [使えるRSpec入門・その4「どんなブラウザ操作も自由自在！逆引きCapybara大辞典」](https://qiita.com/jnchito/items/607f956263c38a5fec24)

### 対象となる読者

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

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

- RSpec 3.1.0
- Ruby 2.1.3

なお、今回の投稿ではRailsは出てきません。
「素のRubyプログラム」を対象にします。

### RSpecのセットアップについて

RSpecのセットアップ方法については、 @yusabana さんが書かれた以下の記事を参考にしてください。

- [RailsじゃないRSpec3環境を構築する方法](https://qiita.com/yusabana/items/db44b81bdddf6ed0e9f5)

**2019.7.22追記：セットアップ動画を作成しました**

上のyusabanaさんの記事をベースにした、(Railsを使わない)RSpec環境の作成方法を動画にしました。
初めてRSpecを使用される方はこちらの動画も参考にしてみてください。
また、動画の作成中に一番外側の`describe`は`RSpec.describe`と書いた方が良いことに気づいたので、この記事（と、その2〜その4の記事）の内容もすべて修正してあります。

[【2019年版】RailsじゃないRspec3環境を構築する方法 \- YouTube](https://www.youtube.com/watch?v=njFJ4FOOKC4&feature=youtu.be)

なお、この動画の実行環境は以下のとおりです。

- ruby 2.6.3
- rspec 3.8.0
- bundler 2.0.2
- rubygems 3.0.3

## 初めの一歩

それではさっそく始めましょう。

### describe / it / expect の役割を理解する

一番単純なRSpecのテストはこんな記述になります。

````ruby
RSpec.describe '四則演算' do
  it '1 + 1 は 2 になること' do
    expect(1 + 1).to eq 2
  end
end
````

`describe` （`RSpec.describe`）はテストのグループ化を宣言します。
ここでは「四則演算に関するテストを書くよー」と宣言しています。
ちなみに describe は日本語にすると、「～を述べる」「～を説明する」「～を記述する」という意味です。

`it` はテストを example という単位にまとめる役割をします。
`it do ... end` の中のエクスペクテーション（期待値と実際の値の比較）がすべてパスすれば、その example はパスしたことになります。

`expect(X).to eq Y` で記述するのがエクスペクテーションです。
expect には「期待する」という意味があるので、 `expect(X).to eq Y` は「XがYに等しくなることを期待する」と読めます。
よって、 `expect(1 + 1).to eq 2` は「1 + 1 が 2 になることを期待する」テストになります。

ちなみに、 `to` と `eq` の部分はマッチャと呼ばれる機能ですが、今回の記事ではあまり深入りしません。
とりあえず今は「期待値と実際の値が等しいことを確認してるんだなー」ということだけ理解してもらえれば大丈夫です。


**参考: should が expect に変更された理由について**

RSpec 2.10以前は `should` を使って次のようなエクスペクテーションを書いていました。

````ruby
(1 + 2).should eq 3
# または
(1 + 2).should == 3
````

`should` ではなく、 `expect` が使われるようになった理由は、 `should` だと稀に不具合が発生することがわかったからです。
`should` はメタプログラミング（いわゆる黒魔術）を使っていたので、問題が発生したんですね。

詳しい理由はこちらに載っています。

[RSpec's New Expectation Syntax (英語)](https://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/)

### 複数の example

`describe` の中には複数の example（`it do ... end`）が書けます。

````ruby
RSpec.describe '四則演算' do
  it '1 + 1 は 2 になること' do
    expect(1 + 1).to eq 2
  end
  it '10 - 1 は 9 になること' do
    expect(10 - 1).to eq 9
  end
end
````

テストを実行すると、以下のような結果が表示されます。

````text
2 examples, 0 failures, 2 passed

Finished in 0.002592 seconds

Process finished with exit code 0
````

上の例では `it do ... end` が2回登場しているので、「2つの example がパスした」と出力されています。

### 複数のエクスペクテーション

1つの example の中に複数のエクスペクテーションを書くのもOKです。

````ruby
RSpec.describe '四則演算' do
  it '全部できること' do
    expect(1 + 2).to eq 3
    expect(10 - 1).to eq 9
    expect(4 * 8).to eq 32
    expect(40 / 5).to eq 8
  end
end
````

ただし、こうしてしまうと途中でテストが失敗したときに、その先のエクスペクテーションがパスするのかしないのかが予想できません。
なので、原則として「1つの example につき1つのエクスペクテーション」で書いた方がテストの保守性が良くなります。（もちろん、原則なので必要であれば破っても構いません）

### ネストした describe

`describe` はいくつでも書けますし、ネストさせることもできます。
基本的な役割は「テストのグループ化」なので、次のようにグループ化することもできます。

````ruby
RSpec.describe '四則演算' do
  describe '足し算' do
    it '1 + 1 は 2 になること' do
      expect(1 + 1).to eq 2
    end
  end
  describe '引き算' do
    it '10 - 1 は 9 になること' do
      expect(10 - 1).to eq 9
    end
  end
end
````

適切にグループ化すると、「この describe ブロックはこの機能をテストしてるんだな」と読み手がテストコードを理解しやすくなります。

なお、一番外側の`describe`以外は`RSpec.`を省略できます。

## context と before でもう少し便利に

もうちょっと実践的な例を使って、 `context` と `before` の使い方を見ていきましょう。

ここではこんなクラスをテストします。

````ruby
class User
  def initialize(name:, age:)
    @name = name
    @age = age
  end
  def greet
    if @age <= 12
      "ぼくは#{@name}だよ。"
    else
      "僕は#{@name}です。"
    end
  end
end
````

「初めの一歩」で学んだ知識を使うと次のようなテストが書けます。

````ruby
RSpec.describe User do
  describe '#greet' do
    it '12歳以下の場合、ひらがなで答えること' do
      user = User.new(name: 'たろう', age: 12)
      expect(user.greet).to eq 'ぼくはたろうだよ。'
    end
    it '13歳以上の場合、漢字で答えること' do
      user = User.new(name: 'たろう', age: 13)
      expect(user.greet).to eq '僕はたろうです。'
    end
  end
end
````

ちなみにgreet は日本語で「あいさつする」の意味です。

このままでも問題ないといえば問題ないのですが、もう少しRSpecらしくテストをリファクタリングしてみましょう。

**参考: 「初めの一歩」とちょっと違うところ**
`describe` には `describe User` のように、文字列ではなくクラスを渡すこともできます。

また、「インスタンスメソッドの greet メソッドをテストしますよ」という意味で `describe '#greet'` のように書くこともよくあります。

### context で条件別にグループ化する

RSpecには `describe` 以外にも `context` という機能でテストをグループ化することもできます。
どちらも機能的には同じですが、 `context` は条件を分けたりするときに使うことが多いです。
ちなみに、contextは日本語で「文脈」や「状況」の意味になります。

ここでは「12歳以下の場合」と「13歳以上の場合」という二つの条件にグループ分けしてみました。

````ruby
RSpec.describe User do
  describe '#greet' do
    context '12歳以下の場合' do
      it 'ひらがなで答えること' do
        user = User.new(name: 'たろう', age: 12)
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      it '漢字で答えること' do
        user = User.new(name: 'たろう', age: 13)
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
````

`describe` と同様、 `context` で適切にグループ化すると、「この context ブロックはこういう条件の場合をテストしてるんだな」と読み手がテストコードを理解しやすくなります。

### before で共通の前準備をする

`before do ... end` で囲まれた部分は example の実行前に毎回呼ばれます。
`before` ブロックの中では、テストを実行する前の共通処理やデータのセットアップ等を行うことが多いです。

少々強引ですが、サンプルコードでは `name: 'たろう'` が重複しているので、DRYにしてみましょう。

````ruby
RSpec.describe User do
  describe '#greet' do
    before do
      @params = { name: 'たろう' }
    end
    context '12歳以下の場合' do
      it 'ひらがなで答えること' do
        user = User.new(**@params.merge(age: 12))
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      it '漢字で答えること' do
        user = User.new(**@params.merge(age: 13))
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
````

上の例を見るとわかるように、 ローカル変数ではなく、インスタンス変数にデータをセットしています。
これは `before` ブロックと `it` ブロックの中では変数のスコープが異なるためです。

（`@params`の前に付いている`**`はハッシュオブジェクトをキーワード引数に変換するために必要なdouble splat演算子です）

### ネストした describe や context の中で before を使う

`before` は `describe` や `context` ごとに用意することができます。
`describe` や `context` がネストしている場合は、親子関係に応じて `before` が順番に呼ばれます。

先ほどのコード例を次のように変えてみましょう。

````ruby
RSpec.describe User do
  describe '#greet' do
    before do
      @params = { name: 'たろう' }
    end
    context '12歳以下の場合' do
      before do
        @params.merge!(age: 12)
      end
      it 'ひらがなで答えること' do
        user = User.new(**@params)
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      before do
        @params.merge!(age: 13)
      end
      it '漢字で答えること' do
        user = User.new(**@params)
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
````

念のため、 `before` がそれぞれどのように呼ばれるのか確認しておいてください。

上の例の場合、 `@params = { name: 'たろう' }` の部分は「12歳以下の場合」でも「13歳以上の場合」でも呼ばれます。
一方、 `@params.merge!(age: 12)` と `@params.merge!(age: 13)` はそれぞれ「12歳以下の場合」と「13以上の場合」の中でしか呼ばれません。

呼ばれる順番はもちろん、親、子の順です。

## 応用（ちょっと高度なテクニック）

ここまでに学んだ内容を使えば、あなたも「ふつうにRSpecでテストを書ける」ようになるはずです。

しかし、あなたの周りの「RSpec熟練者」はこんなテクニックを使っているかもしれません。
念のため、ここから先の内容も知っておきましょう。

### インスタンス変数のかわりに let を使う

先ほど出てきたコード例では、インスタンス変数の `@params` を使っていました。
しかし、RSpecではこのインスタンス変数を `let` という機能で置き換えることができます。

実際に `let` で置き換えたコードを見てみましょう。

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:params) { { name: 'たろう' } }
    context '12歳以下の場合' do
      before do
        params.merge!(age: 12)
      end
      it 'ひらがなで答えること' do
        user = User.new(**params)
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      before do
        params.merge!(age: 13)
      end
      it '漢字で答えること' do
        user = User.new(**params)
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
````

`let(:foo) { ... }` のように書くと、 `{ ... }` の中の値が `foo` として参照できる、というのが `let` の基本的な使い方です。

ただ、上の例では `{ { name: 'たろう' } }` と、 `{ }` が2回出てくるのでややこしくなってます。
外側の `{ }` はRubyのブロックで、 内側の `{ }` はハッシュリテラルです。

わかりづらい方は、こんなふうに書くとイメージが付きやすいかもしれません。

````ruby
# let(:params) { { name: 'たろう' } } と同じ意味のコード
let(:params) do
  hash = {}
  hash[:name] = 'たろう'
  hash
end
````

ところで、みなさんの中には「インスタンス変数のかわりに `let` を使うのって何かメリットがあるの？」と思った人がいるかもしれません。
`let` を使うメリットはこのあとで説明します。

### user を let にする

インスタンス変数だけでなく、ローカル変数を `let` で置き換えるのもアリです。

`user` も `let` で置き換えてみましょう。

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(**params) }
    let(:params) { { name: 'たろう' } }
    context '12歳以下の場合' do
      before do
        params.merge!(age: 12)
      end
      it 'ひらがなで答えること' do
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      before do
        params.merge!(age: 13)
      end
      it '漢字で答えること' do
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
````

重複していた `user = User.new(**params)` の部分を共通化することができました。

### let のメリットを活かして age も let で置き換える

上のコードでは `before` ブロックの中で `params.merge!(age: 12)` みたいなコードを書いているのがあまりクールじゃありません。

どうせなら、ここも `let` で置き換えてスッキリさせましょう。

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(**params) }
    let(:params) { { name: 'たろう', age: age } }
    context '12歳以下の場合' do
      let(:age) { 12 }
      it 'ひらがなで答えること' do
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      let(:age) { 13 }
      it '漢字で答えること' do
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
````

さて、ここで先ほどちらっと触れた「`let` のメリット」が現れています。

`let` は「`before` + インスタンス変数」を使うときとは異なり、 **遅延評価される** という特徴があります。
すなわち、 `let` は必要になる瞬間まで呼び出されません。

上のコード例だと、こんな順番で呼び出されます。

1. `expect(user.greet).to` が呼ばれる => `user` って何だ？
1. `let(:user) { User.new(**params) }` が呼ばれる => `params` って何だ？
1. `let(:params) { { name: 'たろう', age: age } }` が呼ばれる => `age` って何だ？
1. `let(:age) { 12 }` （または13） が呼ばれる
1. 結果として `expect(User.new(name: 'たろう', age: 12).greet).to` を呼んだことになる

これを「`before` + インスタンス変数」で実現しようとすると結構面倒なことになると思います。
というか、僕はその書き方がぱっと思いつきませんでした。

そんなわけで、 `let` の遅延評価されるという特徴をうまく活かすと、効率の良いテストコードを書くことができます。

**2015.5.11追記**
letの利点をRSpecの開発者が説明している記事があったので翻訳してみました。
こちらも参考にしてみてください。

[RSpecのletを使うのはどんなときか？（翻訳）](https://qiita.com/jnchito/items/cdd9eef2ed193267c651)

### subject を使ってテスト対象のオブジェクトを1箇所にまとめる

テスト対象のオブジェクト（またはメソッドの実行結果）が明確に一つに決まっている場合は、 `subject` という機能を使ってテストコードをDRYにすることができます。

たとえば、先ほどのコード例ではどちらのexampleも `user.greet` の実行結果をテストしています。
そこで、 `user.greet` を `subject` に引き上げて、 example の中から消してみましょう。

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(**params) }
    let(:params) { { name: 'たろう', age: age } }
    subject { user.greet }
    context '12歳以下の場合' do
      let(:age) { 12 }
      it 'ひらがなで答えること' do
        is_expected.to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      let(:age) { 13 }
      it '漢字で答えること' do
        is_expected.to eq '僕はたろうです。'
      end
    end
  end
end
````

`subject { user.greet }` を宣言したので、今まで `expect(user.greet).to eq 'ぼくはたろうだよ。'` と書いていた部分が `is_expected.to eq 'ぼくはたろうだよ。'` に変わりました。

さらに、 `it` に渡す文字列（'ひらがなで答えること' など）を省略してみます。

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(**params) }
    let(:params) { { name: 'たろう', age: age } }
    subject { user.greet }
    context '12歳以下の場合' do
      let(:age) { 12 }
      it { is_expected.to eq 'ぼくはたろうだよ。' }
    end
    context '13歳以上の場合' do
      let(:age) { 13 }
      it { is_expected.to eq '僕はたろうです。' }
    end
  end
end
````

`it { is_expected.to eq 'ぼくはたろうだよ。' }` は "it is expected to eq 'ぼくはたろうだよ。'" と、自然な英文っぽく読むことができます。

ちなみに subject は日本語で「主語」や「対象」という意味があります。
こんな書き方はできませんが、 `user.greet { is_expected.to eq 'ぼくはたろうだよ。' }` と考えると、 「subject = テストの主語」と解釈することもできそうです。

### リファクタリングしてテストコードの完成！

ここまでは `params` と `user` を分けていましたが分けるメリットもなさそうなので、 `params` の内容をインライン化してしまいましょう。

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(name: 'たろう', age: age) }
    subject { user.greet }
    context '12歳以下の場合' do
      let(:age) { 12 }
      it { is_expected.to eq 'ぼくはたろうだよ。' }
    end
    context '13歳以上の場合' do
      let(:age) { 13 }
      it { is_expected.to eq '僕はたろうです。' }
    end
  end
end
````

これでいったんこのテストコードは完成です。

（`User.new`の引数はハッシュオブジェクトではなく、純粋なキーワード引数に変わったため、`**`も不要になっています）

### 注意: 技巧的なテストコードは避けましょう

ここまでいろいろなテクニックを紹介していきましたが、無理に `let` や `subject` を多用する必要はありません。

完全にDRYなテストコードを目指してこういった機能を多用すると、過度に技巧的になってしまい、かえってテストコードが読みにくくなる恐れがあります。
なので、テストコードは「DRYさ」よりも「読みやすさ」を大事にしてください。
アプリケーション側のコードとは異なり、多少の重複は許容するようにしましょう。

これは、このあとに紹介する `shared_examples` や `shared_context` といったテクニックに関しても同様です。


## it や context に渡す文字列は英語で書く？日本語で書く？

ここまでは日本人向けに日本語を多用してきましたが、RSpecは `it` や `context` に渡す文字列を英語で書くと英語のテストドキュメント風に見えます。

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

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(**params) }
    let(:params) { { name: 'たろう', age: age } }
    context 'when 12 years old or younger' do
      let(:age) { 12 }
      it 'greets in hiragana' do
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context 'when 13 years old or older' do
      let(:age) { 13 }
      it 'greets in kanji' do
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
````

`context` には 'when ～（～であるとき）' や 'with ～（～がある場合）' 、 'without ～（～がない場合）' といった説明の文字列を渡すと、条件でグループ分けしていることが明確になります。

また、 `it` のうしろにはテストしようとするメソッドの「振る舞い」を表す動詞をつなげて、仕様を英文化してください。

・・・ということができれば理想的なのですが、実際問題、思った通りの英文をさっと書き上げるのはなかなか難しいと思います。

なので、日本人しか読まないテストコードなのであれば、日本語で書いても問題ないと僕は考えています。
しっくり合う英文を考えるのに時間を食うぐらいなら、その時間でテストコードを書いた方が時間の有効利用ができますからね。
それに、がんばって英文を書いても文法がめちゃくちゃで他の人が理解できない、なんていうリスクも出てくるかも・・・。

というわけで、英語が苦手な人は無理せず日本語で書いちゃいましょう。

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(**params) }
    let(:params) { { name: 'たろう', age: age } }
    context '12歳以下の場合' do
      let(:age) { 12 }
      it 'ひらがなで答えること' do
        expect(user.greet).to eq 'ぼくはたろうだよ。'
      end
    end
    context '13歳以上の場合' do
      let(:age) { 13 }
      it '漢字で答えること' do
        expect(user.greet).to eq '僕はたろうです。'
      end
    end
  end
end
````

## it のエイリアス ＝ example と specify

ここまでは `it 'xxx'` の形式でテストを書いてきましたが、 RSpecには `it` の他に全く同じ役割の `example` と `specify` があります。
これら3つのメソッドはエイリアスの関係にあるので、以下のテストコードは内部的には全く同じ意味になります。

```rb
it '1 + 1 は 2 になること' do
  expect(1 + 1).to eq 2
end

specify '1 + 1 は 2 になること' do
  expect(1 + 1).to eq 2
end

example '1 + 1 は 2 になること' do
  expect(1 + 1).to eq 2
end
```

なぜ3種類も同じメソッドがあるのかというと、自然な英文を作るためです。

```rb 
# それ(it)はユーザー名を返す
it 'returns user name' do
  # ...
end

# 会社は社員を持つことを仕様として明記(specify)する
specify 'Company has employees' do
  # ...
end

# fizz_buzzメソッドの実行例(example)
example '#fizz_buzz' do
  # ...
end
```

ただしこの記事では英語ではなく日本語を使っていくので、特に使い分けずに毎回 `it` を使っています。
（まあ `it '1 + 1 は 2 になること'` よりも `specify '1 + 1 は 2 になること'` や `example '1 + 1 は 2 になること'` の方が若干自然かもしれませんが・・・）

**2022.2.9追記**
`it`/`example`/`specify`の使い分けについては、以下の記事でさらに詳しく議論しています。

https://qiita.com/jnchito/items/8766405c06690cfcb32b

## RSpecの高度な機能

`let` や `subject` まで使いこなせれば十分初級レベルをクリアしていると思います。
ただし、それに加えてこういうテクニックも知っていると場合によっては役に立つかもしれません。

### example の再利用: shared_examples と it_behaves_like

上で作ったテストコードに、もうちょっとテストパターンを増やしてみましょう。
12歳と13歳だけでなく、もっと小さい子ども（0歳）や、もっと大きな大人（100歳）にもあいさつしてもらいます。


````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(name: 'たろう', age: age) }
    subject { user.greet }

    context '0歳の場合' do
      let(:age) { 0 }
      it { is_expected.to eq 'ぼくはたろうだよ。' }
    end
    context '12歳の場合' do
      let(:age) { 12 }
      it { is_expected.to eq 'ぼくはたろうだよ。' }
    end

    context '13歳の場合' do
      let(:age) { 13 }
      it { is_expected.to eq '僕はたろうです。' }
    end
    context '100歳の場合' do
      let(:age) { 100 }
      it { is_expected.to eq '僕はたろうです。' }
    end
  end
end
````

見てもらえばわかると思いますが、同じexampleが2回ずつ登場しています。
こういう場合は、 `shared_examples` と `it_behaves_like` という機能を使うと、example を再利用することができます。

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(name: 'たろう', age: age) }
    subject { user.greet }

    shared_examples '子どものあいさつ' do
      it { is_expected.to eq 'ぼくはたろうだよ。' }
    end
    context '0歳の場合' do
      let(:age) { 0 }
      it_behaves_like '子どものあいさつ'
    end
    context '12歳の場合' do
      let(:age) { 12 }
      it_behaves_like '子どものあいさつ'
    end

    shared_examples '大人のあいさつ' do
      it { is_expected.to eq '僕はたろうです。' }
    end
    context '13歳の場合' do
      let(:age) { 13 }
      it_behaves_like '大人のあいさつ'
    end
    context '100歳の場合' do
      let(:age) { 100 }
      it_behaves_like '大人のあいさつ'
    end
  end
end
````

`shared_examples 'foo' do ... end` で再利用したいexampleを定義し、 `it_behaves_like 'foo'` で定義したexampleを呼び出すイメージです。

ちなみに `shared_examples` と `it_behaves_like` は日本語にするとそれぞれ、「共有されているexample」と「～のように振る舞うこと」というふうに訳せます。


### context の再利用: shared_context と include_context

`User` クラスに新しいメソッド、 `child?` を追加してみます。


````ruby
class User
  def initialize(name:, age:)
    @name = name
    @age = age
  end
  def greet
    if child?
      "ぼくは#{@name}だよ。"
    else
      "僕は#{@name}です。"
    end
  end
  def child?
    @age <= 12
  end
end
````

せっかくなので `greet` メソッドだけでなく、 `child?` メソッドもテストしておきましょう。

````ruby
RSpec.describe User do
  describe '#greet' do
    let(:user) { User.new(name: 'たろう', age: age) }
    subject { user.greet }
    context '12歳以下の場合' do
      let(:age) { 12 }
      it { is_expected.to eq 'ぼくはたろうだよ。' }
    end
    context '13歳以上の場合' do
      let(:age) { 13 }
      it { is_expected.to eq '僕はたろうです。' }
    end
  end

  describe '#child?' do
    let(:user) { User.new(name: 'たろう', age: age) }
    subject { user.child? }
    context '12歳以下の場合' do
      let(:age) { 12 }
      it { is_expected.to eq true }
    end
    context '13歳以上の場合' do
      let(:age) { 13 }
      it { is_expected.to eq false }
    end
  end
end
````

テストコードを書いてみると、どちらのテストも「12歳以下の場合」と「13歳以上の場合」で、同じ context が登場しています。

こういう場合は `shared_context` と `include_context` を使うと、 context を再利用することができます。


````ruby
RSpec.describe User do
  let(:user) { User.new(name: 'たろう', age: age) }
  shared_context '12歳の場合' do
    let(:age) { 12 }
  end
  shared_context '13歳の場合' do
    let(:age) { 13 }
  end

  describe '#greet' do
    subject { user.greet }
    context '12歳以下の場合' do
      include_context '12歳の場合'
      it { is_expected.to eq 'ぼくはたろうだよ。' }
    end
    context '13歳以上の場合' do
      include_context '13歳の場合'
      it { is_expected.to eq '僕はたろうです。' }
    end
  end

  describe '#child?' do
    subject { user.child? }
    context '12歳以下の場合' do
      include_context '12歳の場合'
      it { is_expected.to eq true }
    end
    context '13歳以上の場合' do
      include_context '13歳の場合'
      it { is_expected.to eq false }
    end
  end
end
````

`shared_examples` のときと同じように、 `shared_context 'foo' do ... end` で再利用したい context を定義し、 `include_context 'foo'` で定義した context を呼び出すイメージです。

**注意: テストコードはDRYさよりも読みやすさを重視しましょう**
繰り返しになりますが、 `shared_examples` も `shared_context` も多用しすぎると他の人が読んだり、あとで自分が読み直したりしたときに読み手を混乱させる恐れがあります。

テストコードの場合はDRYさよりも読みやすさを重視して、多少の重複は許容する方が良い結果に結びつくはずです。

**参考: be_truthy / be_falsey という書き方もあります**
`it { is_expected.to eq true }` と `it { is_expected.to eq false }` はそれぞれ `it { is_expected.to be_truthy }` と `it { is_expected.to be_falsey }` と書き直すことができます。

この話題はマッチャに関することなので、この記事ではあまり深追いせずに `eq true` と `eq false` を使っています。


### 遅延評価される let と事前に実行される let!

`let` は使いこなすとなかなか便利な機能なのですが、「遅延評価される」という特徴がアダになってわかりづらいテストの失敗を招くことがあります。

たとえば、Railsで次のような `Blog` モデルのテストを書くとテストが失敗します。
（すいません、ここだけRailsのテストを使わせてください）

````ruby
RSpec.describe Blog do
  let(:blog) { Blog.create(title: 'RSpec必勝法', content: 'あとで書く') }
  it 'ブログの取得ができること' do
    expect(Blog.first).to eq blog
  end
end
````

このテストが失敗する理由がわかるでしょうか？

`expect(Blog.first).to eq blog` の部分に注目してください。
`Blog.first` を呼び出した時点ではまだ `let(:blog)` が実行されていないため、レコードがデータベースに保存されていません。
そのため、 `Blog.first` が `nil` を返してしまい、テストが失敗することになります。
（`nil` と `blog` を比較しようとした瞬間にレコードがデータベースに保存されます）

この問題を回避する方法のひとつは、 `before` ブロックの中で明示的に `let(:blog)` を呼び出すことです。
明示的に呼び出すことで、 example の実行前にレコードをデータベースに保存することができます。

````ruby
RSpec.describe Blog do
  let(:blog) { Blog.create(title: 'RSpec必勝法', content: 'あとで書く') }
  before do
    blog # ここでデータベースにレコードを保存する
  end
  it 'ブログの取得ができること' do
    expect(Blog.first).to eq blog
  end
end
````

しかし、こんな書き方をしなくてもこれと同じことをしてくれる `let!` があります。
`let!` を使うと example の実行前に `let!` で定義した値が作られるようになります。

````ruby
RSpec.describe Blog do
  let!(:blog) { Blog.create(title: 'RSpec必勝法', content: 'あとで書く') }
  it 'ブログの取得ができること' do
    expect(Blog.first).to eq blog
  end
end
````

というわけで、 `let` の遅延評価がテストの失敗の原因になっている場合は、かわりに `let!` を使うと便利です。

### どうしても動かないテストに保留マークを付ける: pending 

本来はパスすべきなのに、なぜかどうしてもテストがパスしない・・・こんなときは `pending` で保留マークを付けておきましょう。

````ruby
RSpec.describe '繊細なクラス' do
  it '繊細なテスト' do
    expect(1 + 2).to eq 3

    pending 'この先はなぜかテストが失敗するのであとで直す'
    # パスしないエクスペクテーション（実行される）
    expect(foo).to eq bar
  end
end
````

`pending` が変わっているのは、そこで実行を中断するのではなく、そのまま実行を続けることです。
そして、 **テストが失敗すれば** 成功でも失敗でもない「pending（保留）」としてマークされます。
もし最後までテストがパスした場合は pending にならず、「なんでパスすんねん！！」とRSpecに怒られてテストが失敗します。

ちょっとひねくれた仕様ですが、「いつのまにか直ってた」という怪奇現象を避けることができるので、意外と便利な仕様かもしれません。

### 問答無用でテストの実行を止める: skip

一方、本当にそこでテストの実行を止めたいという場合は `skip` を使います。

````ruby
RSpec.describe '何らかの理由で実行したくないクラス' do
  it '実行したくないテスト' do
    expect(1 + 2).to eq 3

    skip 'とりあえずここで実行を保留'
    # ここから先は実行されない
    expect(foo).to eq bar
  end
end
````

`pending` とは異なり、 `skip` はそこから先は実行せずにテストを pending としてマークします。
とはいえ、個人的には `skip` を使うユースケースがあまり思い浮かびません。
登場頻度は `pending` よりもずっと少ないのではないかと思います。

### 手っ取り早くexample全体をskipさせる: xit

example全体を手っ取り早くskipさせたいときは `it` を `xit` に変更するとそのexampleは実行されなくなります。（pending 扱いになります）

````ruby
RSpec.describe '何らかの理由で実行したくないクラス' do
  xit '実行したくないテスト' do
    expect(1 + 2).to eq 3

    expect(foo).to eq bar
  end
end
````

`xit` だけでなく、`xspecify` や `xexample` も同様にexample全体をskipします。

### グループ全体をまとめてskipさせる: xdescribe / xcontext

`it` だけでなく、 `describe` や `context` にも `x` を付けることができます。

```rb
# グループ全体をskipする
RSpec.xdescribe '四則演算' do
  it '1 + 1 は 2 になること' do
    expect(1 + 1).to eq 2
  end
  it '10 - 1 は 9 になること' do
    expect(10 - 1).to eq 9
  end
end

# グループ全体をskipする
RSpec.xcontext '管理者の場合' do
  it '社員情報を編集できる' do
    # ...
  end
  it '社員情報を削除できる' do
    # ...
  end
end
```

### テストはあとで書く: 中身の無い it

`it 'something' do ... end` の `do ... end` を省略すると、その場合も pending のテストとしてマークされます。

これはメソッドを実装するときに、テストパターンを考えながらそのままドキュメント化（RSpec化）できるので結構便利です。

たとえば、「`User` クラスに `good_bye` メソッドを追加したいなあ」と思った場合は、実装に移る前にこんな感じでRSpec上で仕様を設計することができます（実装前のToDoリストと見なすのも良いかもしれません）。

````ruby
RSpec.describe User do
  describe '#good_bye' do
    context '12歳以下の場合' do
      it 'ひらがなでさよならすること'
    end
    context '13歳以上の場合' do
      it '漢字でさよならすること'
    end
  end
end
````

pending のテストがなくなったときが `good_bye` メソッドの実装が完了したときです。

**参考: これってTDD？**
実装の前にテストを書くからTDD（テスト駆動開発）っぽいですが、厳密にいうと違うと思います。
まず、これは pending なので、レッド（失敗）にはなりません（色でいうとイエローですね）。
あと、TDDでは実装よりもテストコード（テストの中身）を先に書く必要がありますが、ここではどちらを先に書くか特に決めていません。
なので、TDD本来のプロセスとはちょっと異なります。

ちなみに僕の場合、「TDDは使いたいときだけ使う」主義です。
実務でコードを書く場合は以下の3パターンのどれかになります。

- 先に実装してあとでテストを書く
- 先にテストを書いてから実装する（TDD）
- 実装後に手作業と目視で確認して、テストコードは書かない

TDD原理主義（何が何でもTDD）を採用するとかえって開発効率が悪くなる場合も多々あるので、費用対効果が一番高そうな方法をその都度選択している感じです。

## まとめ

というわけで、今回の記事ではRSpecの基本的な構文やよく使う便利機能を紹介してみました。
初心者向けの内容から上級者向けの内容まで一気に説明したので、読んでいたみなさんも大変だったかもしれません。
しかし、これぐらいの内容を押さえておけばRSpecの上級者が書いたコードもだいたい理解できるようになるんじゃないかと思います。

RSpecにはたくさんの機能がありますし、ぱっと見、複雑で奇妙に見える構文も多いです。
しかし、適材適所で（ちょっと奇妙だけど）便利な機能を使い、過度に技巧的なテストコードを避けるようにすれば、「RSpecのおいしいところ」を活かした「読みやすくて保守しやすいテストコード」ができあがるはずです。

これからRSpecをさわる人も、何度か使ったことがある人も、今回の記事を参考にして「RSpecのおいしいところ」を活用できるようになってもらえると嬉しいです。

さて、時間があれば同じような今後もRSpec入門記事として、以下のようなテーマを取り上げたいと考えています。

- よく使うマッチャあれこれ
  - 公開しました！ => [使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5)
- モックの使い方
  - 公開しました！ => [使えるRSpec入門・その3「ゼロからわかるモックを使ったテストの書き方」](https://qiita.com/jnchito/items/640f17e124ab263a54dd)
- よく使うCapybaraのDSL
  - 公開しました！ => [使えるRSpec入門・その4「どんなブラウザ操作も自由自在！逆引きCapybara大辞典」](https://qiita.com/jnchito/items/607f956263c38a5fec24)

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

### あわせて読みたい

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

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

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

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

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

#### この記事の裏話
今回の投稿を書いた動機や、書く時に意識したことをブログにまとめてみました。

[「使えるRSpec入門・その1 RSpecの基本的な構文や便利な機能を理解する」を書いた動機や意識したこととか](http://blog.jnito.com/entry/2014/10/27/085806)

### 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)