Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: システムテストをRSpecで実行する(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

@jnchitoさんの以下の記事も合わせてどうぞ。


  • 2018/01/25: 初版公開
  • 2023/10/12: 更新

Rails: システムテストをRSpecで実行する(翻訳)

RSpec 3.7登場前のfeature spec は、実物(またはヘッドレス)のブラウザ環境でJavaScriptの絡むアプリのやり取りをフルスタックでテストする手段でした。最近リリースされたRSpec 3.7(訳注: 現在はRSpec 4.0です)では、Railsのシステムテストを元にしたsystem specが追加されました。Rails 5.1ではActionDispatch::SystemTestCaseが導入され、実際のブラウザでのテストに使えるようになりました。設定済みのCapybaraラッパーが提供され、Railsフレームワークに組み込まれている多くの機能を利用できるようになりました。設定済みのCapybaraのおかげで、従来は正しく設定するためにトリッキーになりがちだった手動設定の手間が大きく軽減されました。feature specの代わりにsystem specを使うメリットは次のとおりです。

  1. テストが終わるとデータベースの変更が自動でロールバックされるので、database_cleaner gemを用いてロールバックを手動で設定する必要がない。
  2. driven_byを使うと、specごとにブラウザを簡単に切り替えられる。
  3. テストが失敗すると即座にスクリーンショットを自動撮影し、ターミナルにもスクリーンショットをインライン表示する。この機能は事前設定済みなので、capybara-screenshot gemなどが不要になる。

上述のメリットに加えて、RSpecチームがRails 5.1以降ではfeature specよりもsystem specを推奨している点も強調しておきたいと思います。

🔗 RSpecのsystem specをヘッドレスChrome向けにセットアップする

まずはRailsプロジェクトを新規作成しましょう。いつものように--skip-testオプションを追加して、RailsデフォルトのminitestではなくRSpecを使うようにします。

rails --version
Rails 5.2.0.beta2
rails new rspec-system-specs --skip-test --skip-active-storage

セットアップでGemfilerspec-railsを追加する作業以外で最も面倒なのは、ヘッドレスChromeブラウザのテストに必要なgemを見極めることでしょう。必要なgemのリストを以下に示します。

group :development, :test do
  # Capybara system testingとselenium driverのサポートを追加
  gem 'capybara', '~> 2.16.1'
  gem 'selenium-webdriver', '~> 3.8.0'
  # Chromeでのシステムテスト実行に使うchromedriverを簡易インストール
  gem 'chromedriver-helper', '~> 1.1.0'
  gem 'rspec-rails', '~> 3.7.2'
end

chromedriverがインストールされていることを確認します(訳注: 以下はMacでhomebrewを使う場合です)。

brew install chromedriver
chromedriver --version
#> ChromeDriver 2.34.522932 (4140ab217e1ca1bec0c4b4d1b148f3361eb3a03e)

以下を実行してspec/test_helper.rbspec/rails_helper.rbを生成します。

rails g rspec:install

🔗 system specを書く

ここでは単純なhome#indexアクションがroutes.rbのrootとして設定されていて、app/views/home/index.html.erbで以下をレンダリングするとします。

<h1 id="title">Hello World</h1>

最初のsystem specを次のようにspec/system/home_spec.rbに実装できます。

require 'rails_helper'

describe 'Homepage' do
  before do
    driven_by :selenium_chrome_headless
  end

  it 'shows greeting' do
    visit root_url
    expect(page).to have_content 'Hello World'
  end
end

ここではヘッドレスChromeを使いたいので、:selenium_chrome_headlessドライバを設定しています。Capybaraでこの他に提供されている登録済みドライバは、:rack_test:selenium:selenium_chromeです。ブラウザの解像度などの高度な設定オプションについてのドキュメントは、driven_byをご覧ください。

ちゃんと動くかどうか確認します。

$ rspec
Puma starting in single mode...
* Version 3.11.0 (ruby 2.5.0-p0), codename: Love Song
* Min threads: 0, max threads: 4
* Environment: test
* Listening on tcp://127.0.0.1:50713
Use Ctrl-C to stop
.

Finished in 1.41 seconds (files took 2.04 seconds to load)
1 example, 0 failures

個別のspecでドライバを設定したくない場合は、以下のようにspec_helper.rbでデフォルトのグローバル設定を行なえます。

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :selenium_chrome_headless
  end
end

後は必要に応じて個別のspecファイルでデフォルトのドライバ設定を上書きします。

🔗 テスト失敗時のブラウザスクリーンショット

system specで特筆したい点は、specが失敗したときにブラウザのスクリーンショットを自動で撮影し、ターミナルにインライン出力してくれる便利な機能があることです。

spec失敗時

この機能はRailsのシステムテストに組み込まれているので、feature specのようにcapybara-screenshotなどによるサポートを手動で設定する必要がありません。

🔗 JavaScriptのテスト

ここまではサーバー側でのコンテンツレンダリングのテストだけなので、今度はクライアント側のJavaScriptを追加して、JavaScriptが動くブラウザ(ここではヘッドレスChrome)がsystem specで使えることを示してみましょう。

$ ->
  $('#title').text('Hello Universe')

specのアサーションをexpect(page).to have_content 'Hello Universe'に変更すると、クライアント側でのJavaScript変更のspecテストはこれまで同様パスします。

$ rspec
Finished in 1.99 seconds (files took 2.44 seconds to load)
1 example, 0 failures

🔗 データベースの自動ロールバック

上述したように、system specでのデータベース変更は自動的にロールバックされます。ページにデータベース出力を少し追加してテストしてみましょう。

<h1 id="title">Hello World</h1>
<p>
  Planet count: <%= Planet.count %>
</p>

specを変更してレコードをseedし、出力されたレコードのcountのアサーションを行うspec exampleを新たに追加します。

describe 'Homepage' do
  before do
    driven_by :selenium_chrome_headless
    puts 'creating a planet'
    Planet.create!(name: 'Mars')
  end

  it 'shows greeting' do
    visit root_url
    expect(page).to have_content 'Hello Universe'
  end

  it 'shows planet count' do
    visit root_url
    expect(page).to have_content 'Planet count: 1'
  end
end

rspecの結果は次のようになります。

$ rspec --format d
creating a planet
  shows greeting
# spec example追加後のデータベース自動ロールバック
creating a planet
  shows planet count

Finished in 1.52 seconds (files took 1.03 seconds to load)
2 examples, 0 failures

できました!feature specではspec example実行後にデータベースの状態をクリーンアップするためにdatabase_cleaner gemの設定が必要でしたが、これで不要になりました。

本記事のデモに用いた例のソースコードはGitHubに置いてあります。

関連記事

TestProf: Ruby/Railsの遅いテストを診断するgem(翻訳)

ソフトウェアテストでstubを使うコストを考える(翻訳)

Rails: ActiveRecord関連付けのpreload/eager-loadをテストする2つの方法(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。