Rails 5.1のSystemTestCaseを試してみた

  • 49
    いいね
  • 0
    コメント

はじめに

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

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

まとめ

というわけで、この記事ではRails 5.1で導入されたSystemTestCaseの使い方を簡単に説明しました。
Railsが標準でE2Eテストをサポートしたことにより、今後はE2Eテストがより身近なものになってきそうです。
Modelのテストだけでなく、画面の表示やJavaScriptを使った機能のテストも積極的に書いていきましょう!

あわせて読みたい

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

Rails 5.1の変更点まとめ - Qiita