システムスペック
システムスペックとは
- モデルやコントローラが他のモデルやコントローラとうまく連携するかをテストする(統合テスト)
- 実際のユーザーがアプリケーションとどのようにやり取りするかをテストする
- Rails 5.1 以降ではシステムテスト( system test ) という名前で、このタイプのテストがセットアップ時にデフォルトでサポートされている。
システムスペックで使用する gem
ブラウザの操作をシミュレートするために Capybara を使用する。
- Capybara を使うとリンクをクリックしたり、Web フォームを入力したり、画面の表示を検証 したりすることができる。
- Rails 5.1 以降であれば Capybara はすでにインストールされている。( system test で使用するため)
- Capybara には Rails の開発環境で実行可能なジェネ レータは用意されていないため、Capybara が追加されているのはテスト環境のみ
group :test do
gem 'capybara'
.
.
.
end
システムスペックの作成
$ rails g rspec:system projects
require 'rails_helper'
RSpec.describe 'Projects', type: :system do
before do
driven_by(:rack_test)
end
pending "add some scenarios (or delete) #{__FILE__}"
end
上記の状態から、Capybara では click_link や fill_in 、visit といった理解しやすいメソッド(後述)が提供されていて、アプリケーションで 必要な機能のシナリオを以下のように書くことができる。
require 'rails_helper'
RSpec.describe "Projects", type: :system do
before do
driven_by(:rack_test)
end
# ユーザーは新しいプロジェクトを作成する
scenario "user creates a new project" do # scenarioはitへ置き換えることも可能
user = FactoryBot.create(:user)
visit root_path
click_link "Sign in"
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
expect {
click_link "New Project"
fill_in "Name", with: "Test Project"
fill_in "Description", with: "Trying out Capybara"
click_button "Create Project"
expect(page).to have_content "Project was successfully created"
expect(page).to have_content "Test Project"
expect(page).to have_content "Owner: #{user.name}"
}.to change(user.projects, :count).by(1)
end
end
click_button を使うと、起動されたアクションが完了する前に次の処理へ移ってしまうことがある。そこで、上記テストのように click_button を実行した expect{} の内部で最低でも 1 個以上のエクスペクテーションを実行し、処理の完了を待つようにするのが良い。
システムスペックの特徴
ユーザーインターフェースを無視して、パラメータを直接コントローラのメソッドに送信するコントローラースペックと異なり、上記で作成したシステムスペックは以下のようなユーザー操作のステップをシミュレートしている。
- ユーザーでログインする
- ログインしたユーザーが新しいプロジェクトを作成する
- 作成したプロジェクトと処理成功のメッセージが表示される
1 アクションをテストするコントローラスペックとは異なり、このステップは以下の複数のコントローラ・アクションを一つのスペックでカバーする。
- home#index
- sessions#new
- projects#index
- projects#new
- projects#create
Capybara のメソッド
以下のサンプルコードは Capybara の DSL が提供しているその他のメソッドの使用例。
scenario "works with all kinds of HTML elements" do
# ページを開く
visit "/fake/page"
# リンクまたはボタンのラベルをクリックする
click_on "A link or button label"
# チェックボックスのラベルをチェックする
check "A checkbox label"
# チェックボックスのラベルのチェックを外す
uncheck "A checkbox label"
# ラジオボタンのラベルを選択する
choose "A radio button label"
# セレクトメニューからオプションを選択する
select "An option", from: "A select menu"
# ファイルアップロードのラベルでファイルを添付する
attach_file "A file upload label", "/some/file/in/my/test/suite.gif"
# 指定した CSS に一致する要素が存在することを検証する
expect(page).to have_css "h2#subheading"
# 指定したセレクタに一致する要素が存在することを検証する
expect(page).to have_selector "ul li"
# 現在のパスが指定されたパスであることを検証する
expect(page).to have_current_path "/projects/new"
end
セレクタのスコープを制限する
Capybara の within を使って ページの一部分に含まれる要素を操作し, そのスコープを制限することができる。
<!-- click here!ボタンが2つある -->
<div id="node">
<a href="http://nodejs.org">click here!</a>
</div>
<div id="rails">
<a href="http://rubyonrails.org">click here!</a>
</div>
上記のような HTML で、id = rails
のリンクを選択したい場合、以下のように書くことができる。
# idを指定し、スコープを制限する
within "#rails" do
click_link "click here!"
end
このようにすることで、id = rails
のリンクを選択することができる。
Capybara の find メソッド
find メソッドは、ページの要素を検索し、その要素を取り出すことができる。
language = find_field("Programming language").value
expect(language).to eq "Ruby"
find("#fine_print").find("#disclaimer").click
find_button("Publish").click
システムスペックのデバッグ
Capybara のコンソール出力を読めば、どこでテストが失敗したのか調査することができる。
しかし、以下のようなテストは、ユーザーがログインしていないことが原因でテストは失敗するが、出力結果からは原因の一部しかわからない。
# ゲストがプロジェクトを追加する
scenario "guest adds a project" do
visit projects_path
click_link "New Project"
end
Failures:
1) Projects guest adds a project
Failure/Error: click_link "New Project"
Capybara::ElementNotFound:
Unable to find link "New Project" # ページに要求されたリンクがないことのみがわかる
# 残りのスタックトレースは省略 ...
このような時に実際の画面を確認して原因をデバッグしたい。
driven_by メソッドで :rack_test を指定した場合、Capybara は ヘッドレスブラウザ( UI を持たないブラウザ)を使ってテストを実行するため、処理ステップを一つずつ目で確認することはできないが、デバッグ用のメソッドを挿入することで Rails がブラウザに返した HTML を見ることはできる。
scenario "guest adds a project" do
visit projects_path
save_and_open_page # ここでページを保存して確認
click_link "New Project"
end
File saved to
/Users/asumner/code/examples/projects/tmp/capybara/
capybara-201702142134493032685652.html.
Please install the launchy gem to open the file automatically.
guest adds a project (FAILED - 1)
保存されたファイルをブラウザで開くと、実際の画面を確認することができる。(今回であれば、リダイレクトされてログイン画面が表示されていることがわかる)
また、コンソールにも出力されているが以下の gem をインストールすることで、自動でファイルを開くことができる。
group :test do
gem 'launchy'
end
ブラウザを起動する必要がない場合や、ブラウザを起動できないコンテナ環境などでは 代わりに save_page メソッドを使うこともできる。このメソッドを使うと HTML ファイルが tmp/capybara に保存され、ブラウザは起動しない。
JavaScript を使った操作をテスト
ここまで Capybara はシンプルなブラウザシミュレータ(つまりドライバ)の:rack_test
を使って、テストに書かれたタスクを実行していた。
:rack_test
は速くて信頼性が高いが、JavaScript を使った操作をテストすることはできない。
このような場合、selenium-webdriver
gem を使うことで、ブラウザを起動して JavaScript を使った操作をテストすることができる。
この gem は Rails 5.1 以降の Rails にはデフォルトでインストールされていて、Capybara でもデフォルトの JavaScript ドライバになっている。
ドライバの共通設定
使用するドライバは driven_by メソッドを使ってテストごとに変更することができるが、システム全体の共通設定として有効化するには以下のような手順を踏む。
1. spec/support ディレクトリを有効化
以下の行をコメント解除することで、Rspec 関連の設定ファイルを spec/support ディレクトリに配置して読み込むようにできる。
Rails.root.glob('spec/support/**/*.rb').sort.each { |f| require f }
2. ドライバの共通設定をするファイルを作成
ここではブラウザを使った基本的なテストでは高速なRack::Test
ドライバを使い、より 複雑なブラウザ操作が必要な場合は JavaScript が実行可能なドライバ(ここではselenium- webdriver
とChrome
)を設定するようにしている。
このほかに Chrome とやりとりするインターフェースになる ChromeDriver が必要になるが、最新版の selenium-webdriver
を使用すると自動的に適切なバージョンの ChromeDriver を ダウンロードしてくれるため、特別な設定は不要。
RSpec.configure do |config|
config.before(:each, type: :system) do
driven_by :rack_test
end
# js: true を指定することで、JavaScript を使った操作をテストすることができる。
config.before(:each, type: :system, js: true) do
driven_by :selenium_chrome
end
end
3. 実際のテストへタグを付与する
require 'rails_helper'
RSpec.describe "Tasks", type: :system do
# ユーザーがタスクの状態を切り替える
scenario "user toggles a task", js: true do # タグを付与してJSを有効化
user = FactoryBot.create(:user)
project = FactoryBot.create(
:project,
name: "RSpec tutorial",
owner: user
)
task = project.tasks.create!(name: "Finish RSpec tutorial")
visit root_path
click_link "Sign in"
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
click_link "RSpec tutorial"
check "Finish RSpec tutorial"
expect(page).to have_css "label#task_#{task.id}.completed"
expect(task.reload).to be_completed
uncheck "Finish RSpec tutorial"
expect(page).to_not have_css "label#task_#{task.id}.completed"
expect(task.reload).to_not be_completed
end
end
この状態でテストを実行すると Chrome のウィンドウが新しく立ち上がり、実際にブラウザで JS を使った操作が実行される。
JavaScript を使った操作をテストする際の注意点
JavaScript を実行するテストと、Selenium を使うテストのデメリットとしてテストの実行時間の遅さが挙げられる。
JavaScript ドライバはだんだん速くなっているので、いつかRack::Test
と同等のスピードで実行できるようになるかもしれないが、現状では必要なときにだけ、テスト上で JavaScript を有効にすることが推奨される。
ヘッドレスドライバを使用する
テストの実行中にブラウザのウィンドウが開くのはあまり望ましくないケースがよくある。
たとえば、GitHub Actions や Travis CI、Jenkins のような 継続的インテグレーション(CI) 環境で実行する場合、先ほど作ったテストは CLI(コマンドラインインターフェース)上で実行する必要がある。
しかし、CLI 上では新しいウィンドウを開くことはできない。
こういった要件に対応するため、Capybara は ヘッドレスドライバを使用できる。
以下のようにドライバを変更することで、Chrome のウィンドウが表示されることなくテストが完了する。
config.before(:each, type: :system, js: true) do
driven_by :selenium_chrome_headless
end
ページの変化の完了を待ちたい場合
デフォルトでは Capybara はボタン要素の出現やページの変化の際にそれらが表示するまで 2 秒待機する。2 秒待っても表示されなければ諦める。
以下の設定でこの待ち時間を変更することができる。(spec/support
ディレクトリを有効化していない場合は、spec/rails_helper.rb
によって読み込まれる場所に配置)
# 10秒待つ設定
Capybara.default_max_wait_time = 10
しかし、この 変更はテストスイートの実行がさらに遅くなる原因になるかもしれないので注意。もしこの設定を変えたいと思ったら、テストコード上で必要に応じてその都度 using_wait_time を使うこともできる。
# 本当に遅い処理を実行する
scenario "runs a really slow process" do
using_wait_time(15) do
# テストを実行する
end
end
実行時間の待機にsleep
を使用することはアンチパターンとなるので避けるべき。
sleep
は待機時間が固定のため、処理が完了しても待機時間が終了せず不必要に長いテスト時間となってしまう。
スクリーンショットでデバッグする
JavaScript ドライバ(selenium-webdriver)を使用するテストでは、take_screenshot
メソッドを使用することで、テストの実行中にスクリーンショットを撮ることができる。
スクリーンショット画像はデフォルトではtmp/capybara
ディレクトリに保存される。
また、Railsの標準機能としてシステムスペック失敗時にはスクリーンショットが自動的に保存される。
フィーチャースペック
システムスペックが導入される以前はフィーチャースペックと呼ばれるRSpec Rails
gem 独自の機能を使って統合テストを書いていた。
相違点としては以下の通り。
- spec/system ではなく spec/features にファイルを保存する
- describe メソッドではなく feature メソッドを使う
- type: オプションに :system ではなく :feature を指定する
- let や let! のエイリアスとして given や given! が使える
- before のエイリアスとして background が使える
- スクリーンショットを撮る場合、
save_screenshot
は使えるが、take_screenshot
は使えない - テストが失敗してもスクリーンショットは自動的に保存されない(明示的に
save_screenshot
メソッドを呼び出す必要あり)
フィーチャースペックからシステムスペックへの移行
フィーチャースペックはレガシーな機能になりつつあるため、早めにシステムスペックに移行することが推奨される。フィーチャースペックをシステムスペックに移行する場合は次のような手順に従って移行する。
- Rails 5.1以上かつ、RSpec Rails 3.7以上になっていることを確認する
- システムスペックで使用する Capybara、Selenium Webdriver といった gem はなるべく最新のものを使うようにアップデートする
- js: true のタグが指定された場合にドライバが切り替わるように設定を変更する
- spec/features ディレクトリを spec/system にリネームする
- 各スペックのタイプを type: :feature から type: :system に変更する
- 各スペックで使われている feature を describe に変更する
- 各スペックで使われている background を before に変更する
- 各スペックで使われている given / given! を let / let! に変更する
- 各スペックで使われている scenario を it に変更する(この変更は任意)
- spec/rails_helper.rb の config.include などで、type: :feature になっている設定があれば type: :system に変更する
参考文献
この記事は以下の情報を参考にして執筆しました。