はじめに
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
参考
サンプルプロジェクトの作成
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
が作成されます。
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
のコメントを外します。
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を利用することにします。
yubinbango.jsを組み込みます。
<!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を付与します。
<%= 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 %>
実際にブラウザで動作確認します。
ブラウザ上ではちゃんと自動検索できているようです。
郵便番号自動検索機能のテスト
ではテストを書いてみましょう。
テストデータのトランザクション管理もうまく動いているかどうか確認するため、今回はsetup
メソッド内で予めユーザーのデータを作成しておきます。
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
[Screenshot]: tmp/screenshots/failures_test_yubinbango.js.png
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
以下はそのスクリーンショットです。
これを見れば、テストが失敗した原因を調査しやすくなります。
なお、デフォルトでは画面サイズが1400x1400で設定されています。
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
します。
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
を書き換えます。
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してあります。
参考:CapybaraのAPIドキュメント
fill_in
のような画面操作のメソッドは以下のAPIドキュメントに載っています。
assert_text
やhas_field?
のようなマッチャは以下のAPIドキュメントに載っています。
また、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が対応できなくなってしまったようです。
というわけで以下のような手順でドライバをアップデートしました。
- 現在インストールされているChromeのバージョンを調べる
-
Version 57.0.2987.133 (64-bit)
でした - この情報は上のログにも出ています
-
- 現在インストールされているChromeDriverのバージョンを調べる
chromedriver -v
-
2.27.440174
でした - この情報は上のログにも出ています
-
https://sites.google.com/a/chromium.org/chromedriver/ にアクセスし、インストールされているChromeのバージョンに対応したChromeDriver(おそらく最新版)をダウンロードする
- 2017.4.18時点の最新版は
2.29.461585
- 2017.4.18時点の最新版は
- ダウンロードしたChromeDriverをPATHの通ったディレクトリにコピーする
- 例
cp chromedriver /usr/local/bin
- 例
- 念のためChromeDriverのバージョンが新しくなったことを確認する
chromedriver -v
- テストを実行してちゃんと動くことを確認する
もしくは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の主要な変更点はこちらにまとめてあります。