改めてRSpecの目的、方針をまとめてみた

RSpecをしっかり学び直そうと思い、再度RSpecでテストを書く目的や、方針をまとめてみました。
@jnchito さんの記事やYouTubeをベースでの発信をベースに、自分の好みや状況を織り込んだ感じです。

## なぜRSpecか
minitestより広く普及していて、情報量が多いから

## 前提
`良いRSpecとは何か`を突き詰めて議論すると、「正しい/間違い」ではなく、「好き/嫌い」の世界になる。自分やチームにとっての落としどころを決めることが大切。

## 何のためにテストを書くか
以下の負担を軽減して**自分が楽をするため**

- リリース前の動作確認
- 機能追加時の動作確認
- gemやライブラリをアップデートする時の動作確認

## 基本方針
費用対効果が高いテストを書く。
読みやすいテストを書く。

## やらないこと
- 通常の運用では起こりえないことはテストしない
- 「RSpecらしいコード」は追い求めない
    - subject使わない。
    - ワンライナー構文(`it { is_expected...}`)は使わない
    - 厳格な教義に従わない(1つのitの中に必ず1つのアサーション、など)
    - describe、contextでのグループ分けも基本しない
- 費用対効果の低いテストコードは書かない(自作のロジック以外の部分のテスト)
    - 単純な関連づけ(has_many)
    - 単純なバリデーション(presence: true)
    - 自動生成されたコード
- `controller spec`、`request spec`
    - system specで検証できる
    - RailsをAPIサーバーとして使う場合のみrequest specを書く
- 過度なDRYは求めない
    - テストコードはDRYより読みやすさを優先
- プライベートメソッドはテストしない
    - プライベートメソッドもちゃんと実行されるようなテストをパブリックメソッドに対して行う
    - そうすることでプライベートメソッドをいくらリファクタリングしてもテストは壊れなくなる
- テスト駆動開発
    - RSpecに十分に慣れてから検討する
- テストコードの中で、ループ等プログラミングっぽいことはなるべくしない。

## やること
- 使うのは`rspec`、`factorybot`、`capybara`
- `system spec`と`model spec`を書く
    - なるべくcontrollerが薄くなるように処理を書く
    - ユーザーストーリーを追うようにsystem specを書く
        - できるだけcontroller内の分岐を網羅できるようにする
    - リファクタリングして、modelのメソッドをちょうど良い粒度にする
    - modelのメソッドの単体テストで、**想定される**いろんなパターンのテストを書く
- 慣れるまでは、処理を実装するたびにテストを書く
    - 慣れてきたら「この処理はテスト不要」みたいな勘所がわかりそう
- 十分にメリットがある時だけ`before`や`let`を使う
    - 乱用はしない
    - 同じコードをベタ書きした方が良い場合はそうする
- 期待値はベタ書きする
    - `eq price/2`じゃなくて`eq 499`
    - 修正の手間は増えるけど、読みやすくなる
- system specでクリックするボタン等はテスト用のdata属性をつけて対応する
    - ベタ書きだと、labelが無いinput等に対応できない。指定方法は統一したい
    - [Capybaraで変更に強いE2Eテストを書く / TokyuRubyKaigi12 - Speaker Deck](https://speakerdeck.com/yasaichi/tokyurubykaigi12)
- セットするデータは「テストデータ」などとは書かず、実運用で想定される値を書く
- 「◯◯の場合」のような説明はコメントで書く
    - 本来`context`に書くものだけど、めんどい
    - ひたすらitを繰り返し、ネストを減らす
- ログイン用のヘルパーメソッドを作る

```rb:定義
module LoginMacros
  def login(user)
    visit sign_in_path
    fill_in 'email', with: user.email
    fill_in 'password', with: '12345678'
    click_button 'ログイン'
    expect(page).to have_content 'ログインしました。'
  end

  def logout
    click_link 'ログアウト'
    expect(page).to have_content 'ログアウトしました。'
  end

  def act_as(user)
    login user
    yield
    logout
  end
end
```
```rb:呼び出し
describe '掲示板' do
  let(:alice) { create :user }
  let(:bob) { create :user }

  it 'トピックの投稿とコメントの返信' do
    # aliceとして操作
    act_as alice do
      visit root_path
      ...
    end
    # bobとして操作
    act_as bob do
      visit root_path
      ...
    end
  end
end
```

## その他
- テスト内でidは決め打ちしない
- 境界値をテストする
    - 20歳以上が大人」という仕様なら、19歳のときと20歳のときを検証する
- [nezumi](https://github.com/mugi-uno/nezumi)というchrome拡張を使うと、capybaraの記法を確認に便利
- [Better Specs](https://www.betterspecs.org/)という「RSpecはかくあるべき」がまとまっているサイトがある
    - 厳し過ぎたりするから従う必要は無いけど、どこかで目を通してみても良いかも
- `example`と`it`と`specify`は同じメソッド
- `let(:user)`を使った方が、before内で`@user = User.create`とするよりtypoに気付きやすくて良い
- エラーメッセージのテストは、文字列を検証する以外にも、個数やキーを検証する方法もある
- system specとmodel specの特徴、使い分け
    - system spec: controllerもmodelもviewも全部テストできる
    - model spec: 動作が速い、必要なコード量が少ない
    - よくあるパターンをsystem specで書き、それで担保できない部分をmodel specで書く

## TODO
- テスト技法を学ぶ
    - 以下の書籍から「どんなケースのテストを書くべきか」等を学ぶ
        - [知識ゼロから学ぶソフトウェアテスト](https://amzn.to/2EjeyZU)
        - [はじめて学ぶソフトウェアのテスト技法](https://amzn.to/32Hee0e)
        - [ソフトウェアテスト技法練習帳 ~知識を経験に変える40問](https://amzn.to/32Kx8mV)

## 参考
- [【初心者向け】テストコードの方針を考える（何をテストすべきか？どんなテストを書くべきか？） - Qiita](https://qiita.com/jnchito/items/2a5d3e15761fd413657a)
- [サヨナラBetter Specs!? 雑で気楽なRSpecのススメ - Qiita](https://qiita.com/jnchito/items/a90b3b09d008227d3d60)
- [マクロ（ヘルパーメソッド）を定義してフィーチャースペックのユーザー切替えを楽に行う - Qiita](https://qiita.com/jnchito/items/a8360e5e7a829d1e19b2)
- [RSpecビギナーズ!! vol.1~3 - Youtube](https://www.youtube.com/watch?v=4lgaIeKdrR4&t=4s)