自分なりのテストコード（RSpec）の分類と、それぞれを書くときの注意事項

## はじめに
この記事は[LIFULL Advent Calendar 2019](https://qiita.com/advent-calendar/2019/lifull)の12/25分の投稿です。

メリークリスマス！
クリスマスにAdventCalenderを書くようになって早3年目です。
私はクリスマスと無縁のため、毎年せこせこ記事を書いています。リア充爆発しろ。


さて、弊社ではAPIの開発の際テストコードを書くのですが、毎回若手が悩み、相談される箇所があるため、今回はそれについてまとめようと思います。

もしかしたら弊社の開発フローや文化に依存しているかもしれませんので、違和感・誤りなどあればコメント頂ければ幸いです。

## 二行で

- テストコードを大まかに分類すると、ユニットテスト / 機能テスト / E2Eの3つに分かれる
    - 上記、呼び方・表現は言語やフレームワークによって異なる
- それぞれ目的に合わせて、モック・スタブを利用する
    - 「モック・スタブ」の用語については[こちらの記事 - スタブとモックの違い](https://qiita.com/k5trismegistus/items/10ce381d29ab62ca0ea6)
がわかりやすく参考になります。

これだけです。
当たり前やん、という方も多いと思いますが、この記事はテストコード初学者向けとなりますのでご容赦ください。

私自身、初めてテストコード（RSpec）を書き始めたときにかなり迷った部分でもありますし、なかなか腹落ちできる記事にも出会えていないので、きっと誰かの役に立つはず。

## テストコードの分類と注意事項まとめ
記事の中でそれぞれ説明しますが、このテーブルに書いてあることがすべてです。

| 分類 | 目的 | 注意事項 |
|:-:|:-:|:-:|
|ユニットテスト|該当の**メソッド**の**ロジック（責務）**が正しいことを担保する|自身以外のメソッドに依存しない。すべてをモック・スタブする。|
|機能テスト|該当の**クラス**（のパブリックメソッド）の**振る舞い**が正しいことを担保する|自クラスのメソッドはモック・スタブしないこと。他のクラスはモック・スタブする。|
|E2Eテスト|該当の**APIまたはURL**の**振る舞い**が正しいことを担保する|（可能な限り）なにもモック・スタブしない|

それぞれサンプルコード（RSpec）を交えながら、具体的な注意事項に触れていきます。

## ユニットテスト
目的は「該当の**メソッド**の**ロジック（責務）**が正しいことを担保する」です。
これを果たす上で最も重要な注意事項は、**自身以外のメソッドに依存しないこと**です。

サンプルコードを見てみましょう。

```ruby:sample1.rb
class Sample1
  def initialize(hoge:, fuga:)
    @hoge = hoge
    @fuga = fuga
  end

  def valid?
    return false unless hoge_valid?(hoge)
    fuga_valid?(fuga)
  end

  private
  def hoge_valid?
    !@hoge.nil? && @hoge.is_a?(Hash)
  end

  # fuga_valid?も同様に、なんらかロジックを持つ
end
```

この場合、``valid?``の責務は

- ``hoge, fuga``それぞれがvalidかどうかを確かめること

です。
それぞれが**どういうときにvalidか**を知る必要はありません。

そのため``valid?``のユニットテストでは、``(hoge|fuga)_valid?``をスタブします。

```ruby:sample1_spec.rb
describe Sample1 do
  let(:target) { described_class.new(hoge, fuga) }
  let(:hoge) { double('hoge') }
  let(:fuga) { double('fuga') }

  describe '#valid?' do
    subject { target.valid?}

    let(:is_hoge_valid) { false }
    let(:is_fuga_valid) { false }

    before do
      allow(target).to receive(:hoge_valid?).and_return(is_hoge_valid)
      allow(target).to receive(:fuga_valid?).and_return(is_fuga_valid)
    end

    context 'when hoge is invalid' do
      it 'returns false' do
        is_expected.to eq false
      end
    end
    
    context 'when hoge is valid' do
      let(:is_hoge_valid) { true }

      context 'when fuga is invalid' do
        it 'returns false' do
          is_expected.to eq false
        end
      end

      context 'when fuga is valid' do
        let(:is_fuga_valid) { true }

        it 'returns true' do
          is_expected.to eq true
        end
      end
    end
  end
```

他のメソッドの実装ではなく、**振る舞い**に応じて自身の返り値を変更する、という``valid?``の責務のみがテストできています。

こうすることで、例えば``hoge_valid?``のロジックが変更になり、``nil``のときは``true``を返すようになったとしても、``valid?``の実装もテストもなにも変えなくてよいようになります。

## 機能テスト

続いて機能テストです。こちらの目的は、
「該当の**クラス**（のパブリックメソッド）の**振る舞い**が正しいことを担保する」
でした。
機能テストの注意点は、「自クラスのメソッドはスタブしないこと」です。

先程の``Sample1``クラスに少し手を加えて説明します。

```ruby:sample2.rb
class Sample2
  def initialize(hoge:, fuga:)
    @hoge = hoge
    @fuga = fuga
  end

  def status
    return 'OK' if valid?
    'NG'
  end

  private
  def valid?
    return false unless hoge_valid?(hoge)
    fuga_valid?(fuga)
  end
end
```

``status``というメソッドを追加してみました。

この``Sample2``を**利用する側**として知りたいことは、ある``hoge, fuga``を入れたときに``status``が何を返すか、だったりします。

実装上は``valid?``がどういう判定であるか、なのですが、外からはそこは意識したくないですし、そういう内部処理を隠匿してこそ**そのクラスの振る舞い**と言えると思います。

こういった要求をテストコードで実現したものが、**機能テスト**になります。
イメージとしては、golangの[TableDrivenTests](https://github.com/golang/go/wiki/TableDrivenTests)が非常に近いです。

```ruby:samle2_spec.rb
describe Sample2 do
  let(:target) { described_class.new(hoge, fuga) }
  let(:hoge) { nil }
  let(:fuga) { nil }

  describe '#status' do
    subject { target.status }

    context 'when hoge and fuga are nil' do
      it 'returns NG' do
        is_expected.to eq 'NG'
      end
    end

    context 'when hoge and fuga are Hash' do
      let(:hoge) { {hoge: 'hoge'} }
      let(:fuga) { {fuga: 'fuga'} }

      it 'returns OK' do
        is_expected.to eq 'OK'
      end
    end
  end
end
```

付け加えると、場合にもよりますが、**他クラスに依存している場合はスタブする**ようにしましょう。
他クラスへは「振る舞い」にしか依存するべきではありません。

## 次に行く前に
ここまでユニットテストと機能テストについて書きましたが、どうでしょう？
次のように思いませんでしたか？

「機能テストあればユニットテストいらなくね？」

そのとおりです。
基本的には、機能テストが**しっかりと**書けていればユニットテストは不要です。
なぜなら、そのクラスの責務はしっかりと担保されているから。

ただ、それなりに複雑なロジックを書き始めた際、
すべての入力パターンを機能テストで担保できますか？
本当に考慮漏れはないでしょうか？

なので、ユニットテストは機能テストを「補う」ためにあるものだと思ってください。

一つ一つのパーツがしっかりと作ってあり、ロジックが担保されている。
だから、一部代表的な機能がテストできていれば、そのクラスは信頼できる。

という立て付けで考えるといいと思います。

## E2Eテスト
最後にE2Eテストです。こちらの目的は、
「該当の**APIまたはURL**の**振る舞い**が正しいことを担保する」
でした。

ユニットテスト、機能テストと広げて来たのでもはや自明かと思いますが、
E2Eテストの注意点は、「（可能な限り）なにもスタブしないこと」です。

あなたのサービスをユーザーが使うときの振る舞いをテストするものです。
外部API、DB、ロガー、あらゆるものが期待通りに動くかわかりません。
考えうる限りの入力で、APIやウェブページをテストしてください。

まぁ、実際は無理ですよね。
なので、先程書いたように、最重要（あるいは代表的）な部分をE2Eテストで担保して、それを下位のテスト（機能テスト、ユニットテスト）で補完しましょう。

## 最後に
当たり前のことですが、テストコードはプロダクトコードの品質を担保・維持するためのものです。
開発体制により、要求品質は異なるでしょうし、その方法も異なるでしょう。
大事なことは、それぞれのテストの**目的**をきちんと理解し、それに合わせた**方法**で書くことかと思います。

自分の考慮不足に気づけるので、私はユニットテストを書くのが大好きです。