統合テスト
Webアプリケーションの統合テスト
モデルの単体での機能のテストであったり、コントローラの特定のアクションのテストを書く方法までで記述してきたのはあくまで単体テストである。
特定の機能・バリデーションなど、単独では予測した仕様になっていることはテストできているが、それらを組み合わせて利用した際に、意図した仕様になっているかどうかは確かめられていない。
「ログインフォームにメールアドレスとパスワードを入力してボタンを押すとログインできる」「メニューから必要な情報をクリックするととデータベースの情報が変更される」といった仕様は、モデル・コントローラのアクションという単位ではテストできているが、ブラウザ上で該当する操作を行なった時に、期待する動作をしているかどうかはまだテストできていない。
このような「ユーザーが実際にアプリーションを操作する様子を再現して行われるテストは統合テスト
と呼ばれる。Rspecを用いて統合テストを書く手段として、フィーチャスペック
を利用できる。
フィーチャスペック
フィーチャスペック
フィーチャスペック
は、Rspecを使って統合テストを行うためのスペック。テスト環境用の仮想ブラウザを操作して、「特定のa要素をクリックする」「ボタンと対応するコントローラのアクションが動く」といった複雑なテストを書くことができる。細かい機能のテストは既に単体テストで実現しており、アプリケーションの肝となる動作を総合的にテストしたい場合に用いられる。
フィーチャスペックを書くためには、追加でgemを導入する必要がある。
今回はCapybara
というgemを導入して、フィーチャスペックを書いていく。
Capybara
Capybara
は、ブラウザの操作を再現するのに必要なgem。特定の要素をクリックしたり、フォームに値を入力したり、特定の要素が画面に表示されているかなど、様々なブラウザ上の動きをテストすることができる。Capybaraを導入すると、テストを記述するためのヘルパーメソッドが複数追加され、これらを活用してフィーチャスペックを記述していくことになる。以下、代表的なものをいくつか。
visitメソッド
visit
メソッドは引数にURL、 もしくはプレフィックスを指定することで、そのページに移動することができるメソッド。大抵の場合、テストしたいHTML要素・機能のあるページに移動してから他のテスト用の動作を書くことになるため、visitメソッドは重宝する。
click_onメソッド
click_on
メソッドは、指定したHTML要素をクリックするメソッド。引数にはHTML要素のvalue属性を指定する。
1 2 3 4 5 |
# <input type="submit" name="commit" value="こんにちは">という要素をクリック
click_on("こんにちは")
# <a href="/users/new">会員登録する</a>という要素をクリック
click_on("会員登録する")
|
visitメソッドとclick_onメソッドは特によく使用する。
使用するメソッドの違いの他にも、フィーチャスペックには他のテストにはない2つの特徴がある。
(1)一部の記述の名前が単体テストと異なる
フィーチャスペックでは、itの代わりにscenarioを、beforeの代わりにbackgroundと記述する。他にもdescribeをfeatureと記述したり、letをgivenと記述するなどの慣習がある。
これらは全てエイリアスメソッドなので、名前が異なるだけで内部的な実装は全く同じ。また、フィーチャスペックでもit・before・describe・letを使うことはできる。フィーチャスペックらしい書き方をするか、他のスペックと同じ書き方をするかは、現場のコーディング規約に任せて柔軟に対応する。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(2)複数のexpectが同一テスト内に記述される
フィーチャスペックは統合テストなので、1つの動作の結果として、複数のexpectを期待する場合がある。
例えば、ユーザーの更新ボタンを押した時に、「ユーザーの情報が更新されていること」「ユーザーの個人情報を別途管理しているプロフィールテーブルも更新されること」「更新後に遷移するページに特定のidのhtml要素があること」を確かめたい場合などが考えられる。フィーチャスペックを書く際には、1つのテストにつき1つのexpectという考え方ではなく、確かめたいタイミングで何個でもexpectを書くようにする。
1 2 3 4 5 6 7 8 9 10 |
#1つのscenario(it)の内部に、複数のexpectが記述されている
scenario 'update user' do
user = User.last
expect(user).to be_valid
visit current_user_path
click_on('更新する', visible: false)
user.reload
expect(user.female?).to eq true
expect(user.letters).to eq 2
end
|
Pictweetの統合テスト
Pictweetを使ってフィーチャスペックを書いてみる。
Capybaraを導入する
1 2 3 |
group :test, :development do
gem 'capybara'
end
|
1 |
$ bundle install
|
これで、Capybaraを使用する準備ができた。
PicTweetのコードを書き換える
以前作成した状態では、Capybaraがエラーを起こしてしまうため一部分コードを変更する。
変更内容は、imageとtextの入力フォームにidを追加。
以下のどちらに該当するか確認してから変更する。
app/views/tweets/new.html.erbのヘルパーメソッド を確認して、以下のどちらに当てはまるかチェックする。
form_tagを使用している場合
1 2 3 4 5 6 7 8 9 10 11 12 |
<div class="contents row">
<div class="container">
<%= form_tag('/tweets', method: :post) do %>
<h3>
投稿する
</h3>
<input type="text" name="image" placeholder="Image Url" id="image">
<textarea name="text" placeholder="text" rows="10" cols="30" id="text"></textarea>
<input type="submit" value="SEND">
<% end %>
</div>
</div>
|
form_withを使用している場合
1 2 3 4 5 6 7 8 9 10 |
<div class="contents row">
<%= form_with(model: @tweet, local: true) do |form| %>
<h3>
投稿する
</h3>
<%= form.text_field :image, placeholder: "Image Url", id: "image" %>
<%= form.text_area :text, placeholder: "text" , rows: "10", id: "text" %>
<%= form.submit "SEND" %>
<% end %>
</div>
|
作業ファイルを作成
フィーチャスペックを記述するためのファイルを作成する。spec以下にfeaturesというディレクトリを作成し、そこにtweet_spec.rbを作成する。
1 2 3 4 5 6 |
Scenarioを作成する。今回は、ユーザーが実際にログインして、ツイートを投稿できるかどうかをフィーチャスペックで確かめる。
まずはルートにアクセスして、期待するHTML要素が描画されているかどうか確かめる。
ルートにアクセスする処理を再現する
1 2 3 4 5 6 7 8 9 10 11 |
visit root_pathでルートを開くことができる。
have_no_contentマッチャは、引数に指定したバリューを持つHTML要素がそのページに存在しないことを確かめるためのマッチャ。pictweetは、ログインしなければ投稿ボタンが現れない仕様になっているため、ここでは投稿ボタンが存在しないことを確かめている。
ログイン処理を再現する
ブラウザ上でログインする流れを整理すると、次のような4ステップになる。
(1)ログインフォームのあるページに移動する
(2)メールアドレスを入力する
(3)パスワードを入力する
(4)ログインボタンを押す
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
require 'rails_helper'
feature 'tweet', type: :feature do
let(:user) { create(:user) }
scenario 'post tweet' do
# ~省略~
# ログイン処理
visit new_user_session_path
fill_in 'user_email', with: user.email
fill_in 'user_password', with: user.password
find('input[name="commit"]').click
expect(current_path).to eq root_path
expect(page).to have_content('投稿する')
end
end
|
deviseを導入することによって生成されるログインページのプレフィックスは、new_user_session_path。fill_in
メソッドを使って値が入力される動きを作成する。
idがuser_emailのフォームにはlet(:user)
で作成したuserのemail、idがuser_passwordのフォームにはuserのpasswordを入力している。
フォームに必要な情報を入力した後は、find('input[name="commit"]').click
でログインボタンをクリックしている。
ログイン処理後はルートにリダイレクトされるようになっている。expect(current_path).to eq root_path
で、ルートに移動したことを確かめ、expect(page).to have_content('投稿する')
で、ログイン状態では投稿ボタンが表示されることを確認している。
ログイン状態が再現できたので、最後にツイートを作成して投稿できるかをテストする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
require 'rails_helper'
feature 'tweet', type: :feature do
let(:user) { create(:user) }
scenario 'post tweet' do
# ログイン前には投稿ボタンがない
visit root_path
expect(page).to have_no_content('投稿する')
# ログイン処理
visit new_user_session_path
fill_in 'user_email', with: user.email
fill_in 'user_password', with: user.password
find('input[name="commit"]').click
expect(current_path).to eq root_path
expect(page).to have_content('投稿する')
# ツイートの投稿
expect {
click_link('投稿する')
expect(current_path).to eq new_tweet_path
fill_in 'image', with: 'https://s.eximg.jp/expub/feed/Papimami/2016/Papimami_83279/Papimami_83279_1.png'
fill_in 'text', with: 'フィーチャスペックのテスト'
find('input[type="submit"]').click
}.to change(Tweet, :count).by(1)
end
end
|