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
- セットするデータは「テストデータ」などとは書かず、実運用で想定される値を書く
- 「◯◯の場合」のような説明はコメントで書く
- 本来
context
に書くものだけど、めんどい - ひたすらitを繰り返し、ネストを減らす
- 本来
- ログイン用のヘルパーメソッドを作る
定義
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
呼び出し
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というchrome拡張を使うと、capybaraの記法を確認に便利
-
Better Specsという「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
- テスト技法を学ぶ
- 以下の書籍から「どんなケースのテストを書くべきか」等を学ぶ