Edited at

Rails 5.1のSystemTestCaseを試してみた

More than 1 year has passed since last update.


はじめに

Rails 5.1で導入されたSystemTestCaseを使ってみたので、その記録を残しておきます。

この記事ではユーザーマスタメンテ画面で、JavaScriptを使った郵便番号自動検索機能をテストします。


Rails 5.1のSystemTestCaseとは?

いわゆるエンドツーエンド(E2E)テストを実行するためのテストケースクラスです。

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

また、サードパーティgemを使う従来の方式ではDatabaseCleanerやDatabaseRewinderを使って、テストデータのトランザクション管理に関する設定が必要でした。

しかし、SystemTestCaseはフレームワーク内でトランザクション管理をしてくれるので、こうした設定も不要になります。


対象バージョン

この記事では以下のバージョンで動作確認しています。

Rails 5.1はベータ版なので、正式リリースの際は多少仕様が変わる可能性もあります。ご注意ください。


  • Rails 5.1.0.beta1

  • Ruby 2.4.0


Rails 5.1.0.beta1のインストール

まず、Rails 5.1をインストールします。

ただし、Rails 5.1.0.beta1はインストールしようとすると次のようなエラーが出ました。

$ gem install rails -v 5.1.0.beta1

ERROR: While executing gem ... (Gem::DependencyResolutionError)
conflicting dependencies activesupport (= 3.0.0) and activesupport (= 5.1.0.beta1)
Activated activesupport-5.1.0.beta1
which does not match conflicting dependency (= 3.0.0)

Conflicting dependency chains:
rails (= 5.1.0.beta1), 5.1.0.beta1 activated, depends on
activesupport (= 5.1.0.beta1), 5.1.0.beta1 activated

versus:
rails (= 5.1.0.beta1), 5.1.0.beta1 activated, depends on
sprockets-rails (>= 2.0.0), 2.0.0 activated, depends on
actionpack (>= 3.0), 3.0.0 activated, depends on
activesupport (= 3.0.0)

このエラーが出たら次のようなコマンドでインストールするといいようです。

$ ruby -rbundler/inline -e "gemfile(true) do; source 'https://rubygems.org/'; gem 'rails', '5.1.0.beta1'; end"

Fetching gem metadata from https://rubygems.org/..........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 12.0.0
Using concurrent-ruby 1.0.4
(省略)
Using sprockets-rails 3.2.0
Installing rails 5.1.0.beta1

参考

Installing pre release gems is giving conflicting dependency errors · Issue #1653 · rubygems/rubygems


サンプルプロジェクトの作成

Railsのバージョンを確認します。

$ rails -v

Rails 5.1.0.beta1

サンプルプロジェクトを作成します。

$ rails new rails-5-1-sandbox

create
create README.md
create Rakefile
(省略)
run bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted


scaffoldを使った画面作成

次にscaffoldを使って簡単な画面を作ります。

今回は名前と郵便番号と住所を持った、ユーザーのマスタメンテ画面を作ります。

$ rails g scaffold User name postal_code address

$ rails db:migrate

この時点でtest/systemディレクトリが作られ、内部にuser_test.rbが作成されます。


test/system/user_test.rb

require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
# test "visiting the index" do
# visit users_url
#
# assert_selector "h1", text: "User"
# end
end



ChromeDriverのインストール

試しにシステムテストを実行してみましょう。

まず、test/system/user_test.rbのコメントを外します。


test/system/user_test.rb

require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
test "visiting the index" do
visit users_url

assert_selector "h1", text: "User"
end
end


テストを実行します。

$ rake                          

Run options: --seed 1731

# Running:

.......E

Error:
UsersTest#visiting the index:
Selenium::WebDriver::Error::WebDriverError: Unable to find chromedriver. Please download the server from http://chromedriver.storage.googleapis.com/index.html and place it somewhere on your PATH. More info at https://github.com/SeleniumHQ/selenium/wiki/ChromeDriver.

test/system/users_test.rb:5:in `block in <class:UsersTest>'

Error:
UsersTest#visiting the index:
Selenium::WebDriver::Error::WebDriverError: Unable to find chromedriver. Please download the server from http://chromedriver.storage.googleapis.com/index.html and place it somewhere on your PATH. More info at https://github.com/SeleniumHQ/selenium/wiki/ChromeDriver.

bin/rails test test/system/users_test.rb:4

Finished in 0.225936s, 35.4083 runs/s, 39.8343 assertions/s.

8 runs, 9 assertions, 0 failures, 1 errors, 0 skips

するとエラーが出てしまいました。

どうやらChromeDriverというドライバが必要なようです。

https://sites.google.com/a/chromium.org/chromedriver/ にアクセスし、ChromeDriverをダウンロードします。

ただし、ローカルPCにインストールされているChromeのバージョンによって、対応するChromeDriverのバージョンも異なるようなので情報をよく確認してください。

今回は以下の組み合わせで動作確認しています。


  • Chrome 56.0.2924.87

  • ChromeDriver 2.27

ChromeDriverはPATHの通ったディレクトリに配置する必要があります。

以下のコマンドを打つと、自分のマシンのPATHが確認できます。

$ echo $PATH

/usr/local/bin:...

僕は/usr/local/binに配置しました。(どこがベストなのか確信はありませんが)

$ cp chromedriver /usr/local/bin

(2017.7.11追記)Macユーザーの方はHomebrewでインストールするのも良さそうです。

$ brew update && brew install chromedriver

ChromeDriverのセットアップが完了したら、もう一度テストを実行します。

$ rake                                    

Run options: --seed 32591

# Running:

Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:58176
Use Ctrl-C to stop
........

Finished in 2.568361s, 3.1148 runs/s, 5.8403 assertions/s.

8 runs, 15 assertions, 0 failures, 0 errors, 0 skips

今度はちゃんとパスしました。

なお、テストを実行するとChromeが実際に起動して自動実行されます。

ただし、フォーカスを奪われたりはしないので、普通にPCを操作できます。


JavaScriptを使った機能のテスト

次に、ちゃんとJavaScriptを使った機能をテストできるか確認してみましょう。

今回は郵便番号の自動検索機能を付けてみます。


郵便番号自動検索機能の実装

郵便番号の自動検索にはyubinbango.jsを利用することにします。

https://github.com/yubinbango/yubinbango

yubinbango.jsを組み込みます。


app/views/layouts/application.html.erb

<!DOCTYPE html>

<html>
<head>
<!-- 省略 -->
<script src="https://yubinbango.github.io/yubinbango/yubinbango.js" charset="UTF-8"></script>
</head>

<body>
<%= yield %>
</body>
</html>


フォームに対してyubinbango.jsの実行に必要なclassを付与します。


app/views/users/_form.html.erb

<%= form_for(user, html: { class: 'h-adr' }) do |f| %>

<span class="p-country-name" style="display:none;">Japan</span>
<!-- 省略 -->

<div class="field">
<%= f.label :postal_code %>
<%= f.text_field :postal_code, class: 'p-postal-code' %>
</div>

<div class="field">
<%= f.label :address %>
<%= f.text_field :address, class: 'p-region p-locality p-street-address p-extended-address' %>
</div>

<!-- 省略 -->
<% end %>


実際にブラウザで動作確認します。

pcTIxen9DB.gif

ブラウザ上ではちゃんと自動検索できているようです。


郵便番号自動検索機能のテスト

ではテストを書いてみましょう。

テストデータのトランザクション管理もうまく動いているかどうか確認するため、今回はsetupメソッド内で予めユーザーのデータを作成しておきます。


test/system/user_test.rb

require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
setup do
# テストデータの作成
@user = User.create!(name: 'いとう')
end

test "yubinbango.js" do
# User編集画面を開く
visit edit_user_path(@user)

# Nameに"いとう"が入力されていることを検証する
assert has_field?('Name', with: 'いとう')

# 郵便番号を入力
fill_in 'Postal code', with: '158-0083'
# 住所が自動入力されたことを検証する
assert has_field?('Address', with: '東京都世田谷区奥沢')

# 更新実行
click_button 'Update User'

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


テストを実行します。

$ rake

Run options: --seed 53971

# Running:

.......Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:59153
Use Ctrl-C to stop
.

Finished in 2.370198s, 3.3752 runs/s, 6.3286 assertions/s.

8 runs, 15 assertions, 0 failures, 0 errors, 0 skips

ちゃんとパスしましたね!

JavaScriptを使った機能も正しく動いているようです。


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

SystemTestCaseはテスト失敗時にスクリーンショットを自動的に撮ってくれます。

試しに先ほどのテストを書き換えて、わざと失敗させてみましょう。

# 郵便番号を入力

fill_in 'Postal code', with: '158-0083'
# 住所が自動入力されたことを検証する
assert has_field?('Address', with: 'ニューヨーク')

テストを実行します。

$ rake

Run options: --seed 62244

# Running:

Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:59235
Use Ctrl-C to stop

F

Failure:
UsersTest#test_yubinbango.js [/Users/jit/dev/sandbox/rails-5-1-sandbox/test/system/users_test.rb:19]:
Expected false to be truthy.

bin/rails test test/system/users_test.rb:9

.......

Finished in 4.547667s, 1.7591 runs/s, 2.4188 assertions/s.

8 runs, 11 assertions, 1 failures, 0 errors, 0 skips

予想通りテストが失敗しました。

テストが失敗すると、tmp/screenshotsにスクリーンショットが保存されます。

$ ls tmp/screenshots 

failures_test_yubinbango.js.png

以下はそのスクリーンショットです。

これを見れば、テストが失敗した原因を調査しやすくなります。

failures_test_yubinbango.js.png

なお、デフォルトでは画面サイズが1400x1400で設定されています。


test/application_system_test_case.rb

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end



応用:Poltergeistでテストする

デフォルトのSystemTestCaseでは、予めChromeとChromeDriverをインストールしておく必要があります。

ローカルPCで実行する場合はあまり問題になりませんが、CI環境で実行する場合はChromeのインストールがネックになるかもしれません。

そこで、Chromeの代わりにPhantomJS + Poltergesitでテストを実行できるように設定を変更してみます。

まず、ローカルマシンにPhantomJSをインストールします。

Macの場合、Homebrewからインストールできます。

$ brew install phantomjs

続いてGemfileにpoltergeistを追加し、bundle installします。


Gemfile

 group :development, :test do

# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '~> 2.7.0'
- gem 'selenium-webdriver'
+ gem 'poltergeist'
end

それから、test/application_system_test_case.rbを書き換えます。


test/application_system_test_case.rb

 require "test_helper"

+require "capybara/poltergeist"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
- driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
+ driven_by :poltergeist, screen_size: [1400, 1400]
end

これで準備完了です。テストを実行してみましょう。

$ rake

Run options: --seed 38389

# Running:

Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:59623
Use Ctrl-C to stop
........

Finished in 1.908633s, 4.1915 runs/s, 7.8590 assertions/s.

8 runs, 15 assertions, 0 failures, 0 errors, 0 skips

Poltergeistでも同じように実行できました。

テストが失敗したときもChromeの場合と同様にスクリーンショットが保存されます。


サンプルアプリケーションのコード

この記事で使ったサンプルアプリケーションはGitHubにpushしてあります。

https://github.com/JunichiIto/rails-5-1-system-test-sandbox


参考:CapybaraのAPIドキュメント

fill_inのような画面操作のメソッドは以下のAPIドキュメントに載っています。

http://www.rubydoc.info/gems/capybara/2.7.1/Capybara/Node/Actions

assert_texthas_field?のようなマッチャは以下のAPIドキュメントに載っています。

http://www.rubydoc.info/gems/capybara/2.7.1/Capybara/Node/Matchers

また、RSpecでCapybaraを使う場合も基本的な考え方は同じなので、システムテストの書き方に迷ったときは以下の記事が参考になるかもしれません。

使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita


2017.4.18追記:ChromeDriverをアップデートする

この記事を書いてしばらくしてから同じテストを実行してみると、次のようなエラーが出てテストが失敗しました。

Minitest::UnexpectedError: Selenium::WebDriver::Error::UnknownError: unknown error: cannot get automation extension

from unknown error: page could not be found: chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/_generated_background_page.html
(Session info: chrome=57.0.2987.133)
(Driver info: chromedriver=2.27.440174 (e97a722caafc2d3a8b807ee115bfb307f7d2cfd9),platform=Mac OS X 10.12.4 x86_64)
test/system/users_test.rb:11:in `block in <class:UsersTest>'
test/system/users_test.rb:11:in `block in <class:UsersTest>'
Finished in 21.74418s
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips

Process finished with exit code 0

どうもChrome本体が新しくなって、ChromeDriverが対応できなくなってしまったようです。

というわけで以下のような手順でドライバをアップデートしました。


  1. 現在インストールされているChromeのバージョンを調べる



    • Version 57.0.2987.133 (64-bit)でした

    • この情報は上のログにも出ています



  2. 現在インストールされているChromeDriverのバージョンを調べる


    • chromedriver -v


    • 2.27.440174でした

    • この情報は上のログにも出ています




  3. https://sites.google.com/a/chromium.org/chromedriver/ にアクセスし、インストールされているChromeのバージョンに対応したChromeDriver(おそらく最新版)をダウンロードする


    • 2017.4.18時点の最新版は2.29.461585



  4. ダウンロードしたChromeDriverをPATHの通ったディレクトリにコピーする


    • cp chromedriver /usr/local/bin



  5. 念のためChromeDriverのバージョンが新しくなったことを確認する


    • chromedriver -v



  6. テストを実行してちゃんと動くことを確認する

もしくはHomebrewを使って、ChromeDriverをアップデートするのが手っ取り早いかもしれません。

参考: http://brewformulas.org/Chromedriver

今まで動いてたテストが突然エラーになったら、ChromeとChromeDriverのバージョンをチェックしてみてください。


2017.4.19追記:HeadlessモードでChromeを実行する

テストの実行中に画面を表示しないHeadlessモードでChromeを起動することもできます。

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

【暫定版】Rails 5.1のSystemTestCaseでHeadlessモードのChromeを使ってみる - Qiita


まとめ

というわけで、この記事ではRails 5.1で導入されたSystemTestCaseの使い方を簡単に説明しました。

Railsが標準でE2Eテストをサポートしたことにより、今後はE2Eテストがより身近なものになってきそうです。

Modelのテストだけでなく、画面の表示やJavaScriptを使った機能のテストも積極的に書いていきましょう!


あわせて読みたい

Rails 5.1の主要な変更点はこちらにまとめてあります。

Rails 5.1の変更点まとめ - Qiita