LoginSignup
8

More than 1 year has passed since last update.

posted at

updated at

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

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

なぜRSpecか

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

前提

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

何のためにテストを書くか

以下の負担を軽減して自分が楽をするため

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

基本方針

費用対効果が高いテストを書く。
読みやすいテストを書く。

やらないこと

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

やること

  • 使うのはrspecfactorybotcapybara
  • system specmodel specを書く
    • なるべくcontrollerが薄くなるように処理を書く
    • ユーザーストーリーを追うようにsystem specを書く
      • できるだけcontroller内の分岐を網羅できるようにする
    • リファクタリングして、modelのメソッドをちょうど良い粒度にする
    • modelのメソッドの単体テストで、想定されるいろんなパターンのテストを書く
  • 慣れるまでは、処理を実装するたびにテストを書く
    • 慣れてきたら「この処理はテスト不要」みたいな勘所がわかりそう
  • 十分にメリットがある時だけbeforeletを使う
    • 乱用はしない
    • 同じコードをベタ書きした方が良い場合はそうする
  • 期待値はベタ書きする
    • eq price/2じゃなくてeq 499
    • 修正の手間は増えるけど、読みやすくなる
  • system specでクリックするボタン等はテスト用のdata属性をつけて対応する
  • セットするデータは「テストデータ」などとは書かず、実運用で想定される値を書く
  • 「◯◯の場合」のような説明はコメントで書く
    • 本来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はかくあるべき」がまとまっているサイトがある
    • 厳し過ぎたりするから従う必要は無いけど、どこかで目を通してみても良いかも
  • exampleitspecifyは同じメソッド
  • let(:user)を使った方が、before内で@user = User.createとするよりtypoに気付きやすくて良い
  • エラーメッセージのテストは、文字列を検証する以外にも、個数やキーを検証する方法もある
  • system specとmodel specの特徴、使い分け
    • system spec: controllerもmodelもviewも全部テストできる
    • model spec: 動作が速い、必要なコード量が少ない
    • よくあるパターンをsystem specで書き、それで担保できない部分をmodel specで書く

TODO

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8