2
4

Everyday RailsでRspecについて学んだこと

Posted at

ドキュメント形式での出力

.rspec
--format documentation

この設定を適用すると、実行中の各テストケースの名前が出力されるため、どのテストが成功したか、または失敗したかを一目で把握することができます。

これにより、テストの結果が直感的に理解しやすくなり、問題の特定と修正が迅速に行えるようになります。

設定前

設定を加える前は、テストの進捗が.Fで表示され、どのテストが失敗したのかすぐにはわかりません。

....F.....

設定後

--format documentationを設定すると、各テストケースの説明が表示されるため、どのテストが成功し、どのテストが失敗したのかが一目でわかります。

計算
  二つの数字を加算する
    2と2を渡した時
      4と等しいこと
    -1と-1を渡した時
      -2と等しいこと
  二つの数字を減算する
    2と2を渡した時
      0と等しいこと
  二つの数字を乗算する
    3と3を渡した時
      9と等しいこと
  二つの数字を除算する
    9と3を渡した時
    (FAILED - 1)

ベストプラクティスに沿う

Rspecの書き方については、下記のベストプラクティスに従うと読みやすくなります。

もちろんです。以下に日本語の例を挙げます。

期待する結果を能動系で明示的に記述する

不適切な例:

it 'メールアドレスを持って保存されるか' do
  user = User.new(email: 'test@example.com')
  expect(user.save).to be_truthy # 能動系ではなく、結果が漠然としている
end

適切な例:

it '有効なメールアドレスを持つユーザーは保存される' do
  user = User.new(email: 'test@example.com')
  expect(user).to be_valid # 能動的で明確な期待
end

テスト結果を動詞を使って説明する

不適切な例:

it 'メールアドレスの存在' do
  user = User.new(email: nil)
  expect(user).not_to be_valid # 何が期待されるか不明確
end

適切な例:

it 'メールアドレスがない場合、ユーザーは拒否される' do
  user = User.new(email: nil)
  expect(user).not_to be_valid # 明確な行動と結果
end

チェックする結果をテスト一つにつき一個だけにする

不適切な例:

it '有効なメールアドレスとパスワードでユーザーが作成される' do
  user = User.new(email: 'test@example.com', password: 'password')
  expect(user).to be_valid
  expect(user.email).to eq('test@example.com') # 複数のアサーション
  expect(user.password).to eq('password')
end

適切な例:

# ユーザー作成のテスト
it '有効なメールアドレスを持つユーザーは保存される' do
  user = User.new(email: 'test@example.com', password: 'password')
  expect(user).to be_valid
end

# メールアドレスの検証
it 'ユーザーのメールアドレスが正しく設定されている' do
  user = User.create(email: 'test@example.com', password: 'password')
  expect(user.email).to eq('test@example.com')
end

# パスワードの検証
it 'ユーザーのパスワードが正しく設定されている' do
  user = User.create(email: 'test@example.com', password: 'password')
  expect(user.password).to eq('password')
end

describeとcontextの使い分け

この二つにもそれぞれ書く内容には定義があります。

describeの使い方

describeは主に、テストの対象となるメソッドやクラス、機能など「何をテストしているのか」をグルーピングするために使います。

RSpec.describe User do
  describe '#name' do
    it 'ユーザーのフルネームを返す' do
      user = User.new(first_name: '太郎', last_name: '山田')
      expect(user.name).to eq '山田 太郎'
    end
  end
end

contextの使い方

contextは「どのような状況や前提条件でテストをしているのか」を説明するときに使います。contextdescribeの中でさらに状況を細分化して使われることが多いです。

RSpec.describe User do
  describe '#name' do
    context '名前と姓が両方存在する場合' do
      it 'フルネームをスペースで区切って返す' do
        user = User.new(first_name: '太郎', last_name: '山田')
        expect(user.name).to eq '山田 太郎'
      end
    end
    
    context '姓のみが存在する場合' do
      it '姓のみを返す' do
        user = User.new(last_name: '山田')
        expect(user.name).to eq '山田'
      end
    end
  end
end

itとspecifyの使い分け

itspecifyexampleなどのエイリアスがあります。
下記が参考になります。

コントローラのテストは時代遅れ

コントローラのテストは、最近では非推奨とされています。
代わりとしてリクエストスペックの使用が推奨されています。

リクエストスペックを書くタイミング

リクエストスペックは基本的に外部のAPIとの通信をテストするさいに使い、基本的はモデルとシステムテストのみを書くようにします。
これはこのEveryday Railsの著者の伊藤淳一さんが答えた動画などが記載されている記事があります。

システムテストにおけるドライバの適切な使い分け

システムテストを高速かつ効率的に行うには、テストの内容に応じて適切なドライバを選択することが鍵です。主に、「JavaScriptを使うか否か」によってドライバを使い分けます。

  • JavaScriptを使用しないテスト:これらはrack_testドライバで行います。rack_testはJavaScriptの実行をサポートしていない代わりに、非常に高速でテストを実行できます。HTMLコンテンツの生成やフォームの送信など、シンプルな操作をテストする際に適しています。

  • JavaScriptを使用するテストselenium_chromeドライバを使用します。これにより、JavaScriptの動作を含むユーザーインタラクションをエミュレートできますが、テストの実行速度は低下する可能性があります。

Capybaraの設定例

以下のようにRSpecの設定を行うことで、テストのタイプに応じて自動的にドライバを切り替えることができます。

spec/support/capybara.rb
RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    driven_by :selenium_chrome
  end
end

js: trueタグを付けることで、JavaScriptが必要なテストケースでselenium_chromeを使うように指定します。

さらに、selenium_chromeheadlessを使用すると、ブラウザウィンドウを開かずにテストを実行することができるため、テストの実行速度が向上します。テストの大部分が通過するようになったら、ヘッドレスモードへの移行を検討しましょう。

DRYにするためにサポートモジュールを活用する

例えばログイン処理を各テストに何度も書くのはDRY原則に反しています。
メソッド化してもいいですが、よりファイル内のコードを減らすために、サポートモジュールに切り出します。

spec/support/login_macros.rb
module LoginMacros
  def login_as(user)
    visit root_path
    find('.btn-primary.btn-lg', text: 'ログイン').click
    fill_in 'メールアドレス', with: user.email
    fill_in 'パスワード', with: 'password'
    click_button 'ログイン'
    expect(page).to have_content "ログインに成功しました"
  end
end

失敗のテストはまとめる

Rspecは通常、一つの検証が失敗するとそこで停止します。
ですが、aggregate_failuresで囲むと一つ検証(エクスペクテーション)が失敗しても続けて検証します。

aggregate_failuresを使わない場合

RSpec.describe 'Calculator' do
  it 'correctly performs arithmetic' do
    calculator = Calculator.new
    expect(calculator.add(2, 2)).to eq(4)         # 成功
    expect(calculator.subtract(5, 3)).to eq(3)    # 失敗
    expect(calculator.multiply(3, 3)).to eq(9)    # 実行されず
  end
end

aggregate_failuresを使用した場合

RSpec.describe 'Calculator' do
  it 'correctly performs arithmetic', :aggregate_failures do
    calculator = Calculator.new
    expect(calculator.add(2, 2)).to eq(4)         # 成功
    expect(calculator.subtract(5, 3)).to eq(2)    # 失敗
    expect(calculator.multiply(3, 3)).to eq(9)    # 成功
    expect(calculator.divide(9, 3)).to eq(3)      # 成功
  end
end

モデルバリデーションテストの効率化

shoulda-matchersは、モデルバリデーションのテストコードを簡潔に記述することを可能にするgemです。これにより、一般的なバリデーションのテストケースをわずか数行のコードで表現することができます。

導入方法

まず、Gemfileに以下を追加してshoulda-matchers gemをインストールします。

group :test do
  gem 'shoulda-matchers'
end

そして、bundle installを実行してgemをプロジェクトにインストールします。

設定の追加

インストール後、RSpecにshoulda-matchersを認識させるための設定を追加する必要があります。spec/rails_helper.rbまたはspec_helper.rbに以下の設定を追加してください。

spec/rails_helper.rb
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

この設定を行うことで、RSpecはshoulda-matchersを利用する準備が整います。

実装例の確認

実際のコードへの適用例を見ることで、shoulda-matchersの利便性をより深く理解することができます。下記のGitHubリンクでは、私が行った実装を通じて、実際にどのようにshoulda-matchersがテストコードをシンプルにするかを示しています。

GitHub Commit: shoulda-matchersの導入

特定のテストだけ実行させる

focus: trueをテストに追加して、特定のテストケースに焦点を当ててRSpecを実行したい場合は、以下のコマンドを使用します。

rspec --tag focus

これにより、開発中に特定の機能やバグ修正に集中したテストを簡単に行うことができます。

アップロードしたファイルを自動的に削除

下記を設定すると自動で削除されるようになります。

config.after(:suite) do
  FileUtils.rm_rf(ActiveStorage::Blob.service.root)
end

アドバイス

  • まずはコードを書いてからテストを書いてもよい。ただし、セットで行うこと
  • システムスペックから描き始められるようになること
  • テストする時間を作ること
  • 常にシンプルであること
2
4
0

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
  3. You can use dark theme
What you can do with signing up
2
4