Ruby
Rails
RSpec

rspec-rails 3.7の新機能!System Specを使ってみた


はじめに

先日、RSpec 3.7がリリースされました。

参考: RSpec 3.7 has been released!

上記ブログの中で「今回のリリースはRailsのSystem Testの統合機能をいち早く使ってもらうためのリリースだ」と書いてあります。

実際、ブログの中で触れられている新機能は「System Spec」機能の追加だけです。

というわけで、この記事はrspec-rails 3.7で導入されたSystem Specの紹介と使い方の説明をしていきます。


実行環境

この記事は以下のバージョンを対象にして書かれています。


  • rspec-rails 3.7.1

  • Rails 5.1.4

  • Ruby 2.4.2

  • selenium-webdriver 3.6.0

  • Capybara 2.15.4

  • Chrome 62

  • ChromeDriver 2.33


サンプルコード

この記事で使用したコードはこちらに置いてあります。

https://github.com/JunichiIto/rails-5-1-system-test-sandbox/tree/rspec-3-7


System Specとは?

Rails 5.1ではSystemTestCaseという新機能が導入されました。

これはいわゆるエンドツーエンド(E2E)テストを実行するための新機能です。

このテストケースクラスを使うと、JavaScriptを利用する画面のテストが書けるようになります。

参考: Rails 5.1のSystemTestCaseを試してみた - Qiita

RailsではMinitestがデフォルトのテスティングフレームワークであるため、SystemTestCaseでもMinitestを使います。

rspec-rails 3.7で導入されたSystem Specは、このSystemTestCaseをRSpecから利用するための新機能です。


Feature Specと何が違うの?どっちを使えばいいの?

rspec-railsではすでにFeature Spec(フィーチャスペック)というE2Eテスト用の機能が用意されています。

System SpecもE2Eテスト用の機能なので、単純に考えるとE2Eテスト用の機能が複数存在することになります。

実際、この両者はよく似ているのですが、System SpecはRails標準のSystemTestCaseをラップしているため、以下のような違いがあります。


  • デフォルトでselnium-webdriver + Chrome上でテストが実行される。

  • DatabaseCleanerやDatabaseRewinderを使ってトランザクション管理をする必要がない。

  • テストが失敗すると自動的にtmp/screenshotsディレクトリにスクリーンショットを保存してくれる。

まだ本格的には使い込んでいませんが、ざっと見た感じでは「もうFeature SpecやDatabaseCleanerはいらなくなるのでは?」と思っています。

冒頭で紹介した公式ブログでも「Rails 5.1を使っている場合はFeature SpecよりもSystem Specの使用を推奨します」と書いてあります。


System Specを使ってみる

ではここから、System Specを実際に使う方法を説明していきます。


使用するサンプルアプリケーション

今回は以下のようなサンプルアプリケーションでSystem Specを実行してみます。

このアプリケーションでは郵便番号を入力すると、JavaScriptによって自動的に住所が入力されるようになっています。

y9W2aFx3zv.gif

コードはGitHubに置いてあります。

https://github.com/JunichiIto/rails-5-1-system-test-sandbox/tree/rspec-3-7


ChromeとChromeDriverをインストールする

System SpecではChromeとChromeDriverをマシンにインストールする必要があります。

どちらも最新版をインストールするようにしましょう。

ChromeDriverのインストールはいくつか方法があります。

# 新規インストール

$ brew install chromedriver

# 最新版にアップデート
$ brew upgrade chromedriver



  • chromedriver-helper gemを使う。 webdrivers gemを使う

今回は一番手軽なchromedriver-helper gemを使うことにします。Gemfileにchromedriver-helperを追加して、bundle installしてください。

chromedriver-helperはサポートが終了しました。

代わりにwebdriversの使用が推奨されています。

詳しくは以下の記事をご覧ください。

サポートが終了したchromedriver-helperからwebdrivers gemに移行する手順 - Qiita


Rails 5.1以上であることを確認する

System SpecはRailsのSystemTestCaseを利用するため、Rails 5.1以上であることが必須です。


Gemfile

gem 'rails', '~> 5.1.4'



テスト関連のgemをインストール/アップデートする

テストに必要なgemを追加して、bundle installします。


Gemfile

group :development, :test do

# ...
gem 'rspec-rails'
end

group :test do
# ...
gem 'capybara'
gem 'selenium-webdriver'
end


これまでRSpecを使っていなかった場合は、以下のコマンドで必要なファイルも追加してください。

$ rails generate rspec:install

既存のプロジェクトですでにテスト用のgemがインストールされていた場合は、以下のコマンドで各種gemを最新版にアップデートしておきましょう。

$ bundle update rspec-rails capybara selenium-webdriver


System Specを書く

System Specを配置するspec/systemディレクトリを作ります。

$ mkdir spec/system

今回はusers_spec.rbというファイルにSystem Specを書きます。

$ touch spec/system/users_spec.rb

users_spec.rbに次のようなコードを書きます。


spec/system/users_spec.rb

require 'rails_helper'

RSpec.describe 'Users', type: :system do
before do
@user = User.create!(name: 'いとう')
end

it 'completes yubinbango automatically with JS' do
# User編集画面を開く
visit edit_user_path(@user)

# Nameに"いとう"が入力されていることを検証する
expect(page).to have_field '名前', with: 'いとう'

# 郵便番号を入力
fill_in '郵便番号', with: '158-0083'
# 住所が自動入力されたことを検証する
expect(page).to have_field '住所', with: '東京都世田谷区奥沢'

# 更新実行
click_button 'Update User'

# 正しく更新されていること(=画面の表示が正しいこと)を検証する
expect(page).to have_content 'User was successfully updated.'
expect(page).to have_content 'いとう'
expect(page).to have_content '158-0083'
expect(page).to have_content '東京都世田谷区奥沢'
end
end


3行目でtype: :systemと書いてあるのがポイントです。

これでこのテストがSystem Specであることを宣言しています。

それ以外は基本的にFeature Specと書き方は変わりません。


System Specを実行する

テストが書き終わったら、テストを実行してみましょう。

実行方法は通常のRSpecの場合と変わりません。

$ rails spec

セットアップがうまくいっていれば、自動的にChromeが起動してSystem Testで書いた内容を実行してくれるはずです。

Feature Specでは必要だったDatabaseCleaner/DatabaseRewinder用の設定や、rails_helper.rbでのCapybara.javascript_driverの指定は不要になります。


応用編


describe/itではなく、feature/scenarioを使う

サンプルコードではdescribe/itを使っていますが、Feature Specと同様にfeature/scenarioを使って書くこともできます。

require 'rails_helper'

RSpec.feature 'Users', type: :system do
background do
@user = User.create!(name: 'いとう')
end

scenario 'completes yubinbango automatically with JS' do
# User編集画面を開く
visit edit_user_path(@user)

# Nameに"いとう"が入力されていることを検証する
expect(page).to have_field '名前', with: 'いとう'

# 郵便番号を入力
fill_in '郵便番号', with: '158-0083'
# 住所が自動入力されたことを検証する
expect(page).to have_field '住所', with: '東京都世田谷区奥沢'

# 更新実行
click_button 'Update User'

# 正しく更新されていること(=画面の表示が正しいこと)を検証する
expect(page).to have_content 'User was successfully updated.'
expect(page).to have_content 'いとう'
expect(page).to have_content '158-0083'
expect(page).to have_content '東京都世田谷区奥沢'
end
end


ヘッドレスモードのChromeで実行する

ヘッドレスモード(画面を起動しないモード)でChromeを実行する場合は、rails_helper.rbに次の設定を追加します。


spec/rails_helper.rb

RSpec.configure do |config|

# ...

config.before(:each) do |example|
if example.metadata[:type] == :system
caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => %w(--headless --disable-gpu)})
driven_by :selenium, using: :chrome, screen_size: [1400, 1400], options: { desired_capabilities: caps }
end
end
end


2017.10.29 追記: 上の設定をもっとシンプルに書く

こちらのブログを参考にしたら、次のように簡単に書くことができました。


spec/rails_helper.rb

RSpec.configure do |config|

# ...

config.before(:each) do |example|
if example.metadata[:type] == :system
driven_by :selenium_chrome_headless, screen_size: [1400, 1400]
end
end
end


2018.7.1 追記: Rails 5.2 + rspec-rails 3.7の場合

Rails 5.2.0 + rspec-rails 3.7.2の環境だと、オプションの渡し方が次のように変更されていました(参考)。


spec/rails_helper.rb

RSpec.configure do |config|

# ...

config.before(:each) do |example|
if example.metadata[:type] == :system
driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
end
end
end



js: trueのときだけ、Chromeを起動する

System Specは毎回Chromeが起動しますが、JavaScriptがなくても実行可能なテストであれば、Chromeなしで検証した方が速度面で有利です。

rails_helper.rbに次のような設定を書けば、Feature Specのときと同じように、js: trueのタグが付いているときだけChromeが起動するようになります。


spec/rails_helper.rb

RSpec.configure do |config|

# ...

config.before(:each) do |example|
if example.metadata[:type] == :system
if example.metadata[:js]
driven_by :selenium_chrome_headless, screen_size: [1400, 1400]
else
driven_by :rack_test
end
end
end
end


System SpecにもJSを使う必要があるテストにだけ、js: trueのタグを付けます。

require 'rails_helper'

RSpec.describe 'Users', type: :system do
# ...

it 'completes yubinbango automatically with JS', js: true do
# JSを使う必要があるテストを書く
end

it 'does not complete yubinbango automatically without JS' do
# JSを使わずに済むテストを書く
end
end


テスト失敗時のスクリーンショットを確認する

テストが途中で失敗した場合は、tmp/screenshotsディレクトリにスクリーンショットが保存されます。

Screen Shot 2017-10-24 at 7.02.15.png


CIでSystem Specを実行する

参考までにTravis CIとCircleCIでSystem Specを実行する場合の設定例を載せておきます。

いずれも「Chromeはヘッドレスモードで起動」「ChromeDriverはchromedriver-helperでインストール」する場合の設定です。

必要なのは最新版のChromeをインストールすることだけですね。


.travis.yml

sudo: required

dist: trusty
language: ruby
rvm:
- 2.4.2
cache: bundler
bundler_args: --without production --deployment

before_install:
- gem install bundler --pre

# Install latest Chrome
- sudo apt-get update
- sudo apt-get install -y libappindicator1 fonts-liberation
- export CHROME_BIN=/usr/bin/google-chrome
- wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- sudo dpkg -i google-chrome*.deb
script:
- bundle exec rspec spec



circle.yml

general:

artifacts:
- "tmp/capybara"
machine:
timezone:
Asia/Tokyo
ruby:
version:
2.4.2
test:
override:
- bundle exec rspec spec
dependencies:
pre:
# Install latest Chrome
- sudo apt-get update
- sudo apt-get install -y libappindicator1 fonts-liberation
- export CHROME_BIN=/usr/bin/google-chrome
- wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- sudo dpkg -i google-chrome*.deb

こちらはCircleCI 2.0用の設定例です。


.circleci/config.yml

version: 2

jobs:
build:
docker:
- image: circleci/ruby:2.4.2-node-browsers
working_directory: ~/rails-5-1-sandbox
steps:
- checkout
- run:
name: Install System Dependencies
command: |
sudo apt-get update
sudo apt-get install -y libappindicator1 fonts-liberation
export CHROME_BIN=/usr/bin/google-chrome
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome*.deb

- restore_cache:
keys:
- v1-dependencies-{{ checksum "Gemfile.lock" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run:
name: Bundle Install
command: |
bundle install --jobs=4 --retry=3 --path vendor/bundle
- save_cache:
paths:
- vendor/bundle
key: v1-dependencies-{{ checksum "Gemfile.lock" }}

- run:
name: Create DB
command: bundle exec rake db:create db:schema:load --trace
- run:
name: Run Tests
command: RAILS_ENV=test bundle exec rspec
- store_test_results:
path: /tmp/test-results


CircleCI 2.0の設定例についてはこちらのブログも参考になると思います。


まとめ

というわけで、この記事ではrspec-rails 3.7で導入されたSystem Specの使い方を説明しました。

従来のFeature Specと一緒と言えば一緒なんですが、System SpecはRailsが標準で用意してくれているSystemTestCaseをラップしてくれているので、そのぶん手軽に導入でき、なおかつスクリーンショットの自動撮影のような便利な機能も使えるようになっています。

僕自身はまだ本格的には使い込んでいないものの、これなら今後は全部System Specに置き換えていっても大丈夫そうだなと感じています。

みなさんもぜひrspec-rails 3.7のSystem Specを試してみてください!


あわせて読みたい

System Specでもブラウザの実行や画面要素の検証ではCapybaraを使います。

Capybaraの詳しい使い方についてはこちらの記事が参考になると思います。

「そもそもRSpecがよくわかってないんだけど・・・」という方は、こちらの入門記事をご覧ください。