Rspecよく分からーんとお悩みのそこのあなたへ

## はじめに

チームで開発している時にメンバーから
「Rspecよく分からーん」
という悩みの声があがりました。
確かに僕が初めてRspecに触れた時にも同じような感想を抱いた覚えたありました。
Rspecが何故分かりにくいのか説明し、じゃあどうしたら分かるようになるか？を解説していきます！

## 環境

今回の例をテストできる環境を用意しましたので手元で実際に試しながらやると理解が捗ると思います。
[GitHubはこちら](https://github.com/yamamoto-hiroya/rspec_practice.git)
[DockerHubはこちら](https://hub.docker.com/r/hiroyayamamoto/rspec_practice)

### 実行手順

githubのREADMEに記載の通りです。

```bash
git clone https://github.com/yamamoto-hiroya/rspec_practice.git
cd rspec_practice/
docker-compose up -d
docker exec -it rspec_practice bash

// 以下docker内
cd /tmp
rspec sample1_spec.rb
```

↓以下のように実行できていれば成功です。

```bash:結果
root@a6244fe37de9:/tmp# rspec sample1_spec.rb
.F

Failures:

  1) sample1_test test false?????
     Failure/Error: expect(true).to be false

       expected false
            got true
     # ./sample1_spec.rb:11:in `block (3 levels) in <top (required)>'

Finished in 0.01019 seconds (files took 0.07753 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./sample1_spec.rb:10 # sample1_test test falseを返すこと
```


## 例

```ruby:sample2_spec.rb
# coding: utf-8
describe 'sample2_test' do
  # テストしたいメソッド
  def get_pokemon_name(no)
    pokemons = {1 => 'フシギダネ', 4 => 'ヒトカゲ', 7 => 'ゼニガメ'}
    pokemons[no]
  end

  describe 'get_pokemon_name' do
    subject { get_pokemon_name(no) }
    shared_examples 'noに該当するポケモンの名前を返すテスト' do
      it 'noに該当するポケモンの名前を返すこと' do
        expect(subject).to eq my_expect
      end
    end

    context '1の場合' do
      let(:no) { 1 }
      let(:my_expect) { 'フシギダネ' }
      it_behaves_like 'noに該当するポケモンの名前を返すテスト'
    end

    context '4の場合' do
      let(:no) { 4 }
      let(:my_expect) { 'ヒトカゲ' }
      it_behaves_like 'noに該当するポケモンの名前を返すテスト'
    end

    context '7の場合' do
      let(:no) { 7 }
      let(:my_expect) { 'ゼニガメ' }
      it_behaves_like 'noに該当するポケモンの名前を返すテスト'
    end

  end
end
```

初心者であればこれを初見で見た時に「なるほどよく分からん」となるのではないでしょうか？
大丈夫です、私もそう思います。

## 何故分かりにくいか

これは色んな共通化を行った最終形を見せられているため分かりにくいのです。
具体的に見ていくと
`subject`, `shared_examples`, `let`, `it_behaves_like`
このあたりがRspec特有の記法のため何をやっているか初見だと分かりにくい要因かなと思っています。

## じゃあどうしたら分かるようになるか？

このテストが出来上がる過程を見ていけば分かりやすいです。
順を追って見ていきましょう。

### 第1歩目

```ruby:sample3_spec.rb
# coding: utf-8
describe 'sample3_test' do
  # テストしたいメソッド
  def get_pokemon_name(no)
    pokemons = {1 => 'フシギダネ', 4 => 'ヒトカゲ', 7 => 'ゼニガメ'}
    pokemons[no]
  end

  describe 'get_pokemon_name' do
    context '1の場合' do
      it 'フシギダネを返すこと' do
        result = get_pokemon_name 1
        expect(result).to eq 'フシギダネ'
      end
    end
  end
end
```

まず1ケース目を愚直に書きます。
この状態であれば
`subject`, `shared_examples`, `let`, `it_behaves_like`
これら全て出てこないためRspecを知らなくてもユニットテストを書いたことがある人であればまぁ何となくは分かるかなと思います。

### 第2歩目

```ruby:sample4_spec.rb
# coding: utf-8
describe 'sample4_test' do
  # テストしたいメソッド
  def get_pokemon_name(no)
    pokemons = {1 => 'フシギダネ', 4 => 'ヒトカゲ', 7 => 'ゼニガメ'}
    pokemons[no]
  end

  describe 'get_pokemon_name' do
    context '1の場合' do
      it 'フシギダネを返すこと' do
        result = get_pokemon_name 1
        expect(result).to eq 'フシギダネ'
      end
    end

    context '4の場合' do
      it 'ヒトカゲを返すこと' do
        result = get_pokemon_name 4
        expect(result).to eq 'ヒトカゲ'
      end
    end
  end
end
```

2ケース目を愚直にコピペします。
ここまでも分かると思います。

### 第3歩目

```ruby:sample5_spec.rb
# coding: utf-8
describe 'sample5_test' do
  # テストしたいメソッド
  def get_pokemon_name(no)
    pokemons = {1 => 'フシギダネ', 4 => 'ヒトカゲ', 7 => 'ゼニガメ'}
    pokemons[no]
  end

  describe 'get_pokemon_name' do
    context '1の場合' do
      let(:no) { 1 }
      let(:my_expect) { 'フシギダネ' }
      it 'フシギダネを返すこと' do
        expect(get_pokemon_name(no)).to eq my_expect
      end
    end

    context '4の場合' do
      let(:no) { 4 }
      let(:my_expect) { 'ヒトカゲ' }
      it 'ヒトカゲを返すこと' do
        expect(get_pokemon_name(no)).to eq my_expect
      end
    end

    context '7の場合' do
      let(:no) { 7 }
      let(:my_expect) { 'ゼニガメ' }
      it 'ゼニガメを返すこと' do
        expect(get_pokemon_name(no)).to eq my_expect
      end
    end

  end
end
```

ここで初めてletが出てきました。
そのテストケースで使う変数をletに定義しています。

### 第4歩目

```ruby:sample6_spec.rb
# coding: utf-8
describe 'sample6_test' do
  # テストしたいメソッド
  def get_pokemon_name(no)
    pokemons = {1 => 'フシギダネ', 4 => 'ヒトカゲ', 7 => 'ゼニガメ'}
    pokemons[no]
  end

  describe 'get_pokemon_name' do
    subject { get_pokemon_name(no) }
    context '1の場合' do
      let(:no) { 1 }
      let(:my_expect) { 'フシギダネ' }
      it 'フシギダネを返すこと' do
        expect(subject).to eq my_expect
      end
    end

    context '4の場合' do
      let(:no) { 4 }
      let(:my_expect) { 'ヒトカゲ' }
      it 'ヒトカゲを返すこと' do
        expect(subject).to eq my_expect
      end
    end

    context '7の場合' do
      let(:no) { 7 }
      let(:my_expect) { 'ゼニガメ' }
      it 'ゼニガメを返すこと' do
        expect(subject).to eq my_expect
      end
    end

  end
end
```

次にsubjectにしました。
今回の主題はget_pokemon_nameの返り値なのでこれをsubjectとしています。

### 第5歩目

```ruby:sample7_spec.rb
# coding: utf-8
describe 'sample7_test' do
  # テストしたいメソッド
  def get_pokemon_name(no)
    pokemons = {1 => 'フシギダネ', 4 => 'ヒトカゲ', 7 => 'ゼニガメ'}
    pokemons[no]
  end

  describe 'get_pokemon_name' do
    subject { get_pokemon_name(no) }
    shared_examples 'noに該当するポケモンの名前を返すテスト' do
      it 'noに該当するポケモンの名前を返すこと' do
        expect(subject).to eq my_expect
      end
    end

    context '1の場合' do
      let(:no) { 1 }
      let(:my_expect) { 'フシギダネ' }
      it_behaves_like 'noに該当するポケモンの名前を返すテスト'
    end

    context '4の場合' do
      let(:no) { 4 }
      let(:my_expect) { 'ヒトカゲ' }
      it_behaves_like 'noに該当するポケモンの名前を返すテスト'
    end

    context '7の場合' do
      let(:no) { 7 }
      let(:my_expect) { 'ゼニガメ' }
      it_behaves_like 'noに該当するポケモンの名前を返すテスト'
    end

  end
end
```

最後にshared_examplesにテストをまとめました。
shared_examplesとit_behaves_likeはセットで使います。
これが1番最初に見せた状態になっています。

## まとめ

今回の例で言うと5段階の共通化を経た状態が完成形となっているため、初見で見ると「なんじゃこりゃ？」となってしまうのではないかなというのが僕の考えです。
ただ共通化の過程が分かると少しは理解しやすいのではないかなと思います。

個人的にはテストコードの共通化はある程度で良いと思っています。
新しくプロジェクトにジョインした人が読めないよりは
ある程度読めてコピペでテストケースが追加できるような状態の方が優しい（易しい）んじゃないかなーと思います。
（これは個人的な感想で賛否あると思います。）
この記事で少しでもRspecが分かるようになったら嬉しいです！