概要
- 前回: Rails5「中級」チュートリアル(3-2)投稿機能: ヘルパー(翻訳)
- 次回: Rails5「中級」チュートリアル(3-4)投稿機能: メインフィード(翻訳)
概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: The Ultimate Intermediate Ruby on Rails Tutorial: Let’s Create an Entire App!
- 原文公開日: 2017/12/17
- 著者: Domantas G
Rails5中級チュートリアルはセットアップが短めで、RDBMSにはPostgreSQL、テストにはRSpecを用います。
原文が非常に長いので分割します。章ごとのリンクは順次追加します。
翻訳時にRuby 2.5.0とRails 5.1.4で動作を確認しています。
注意: Rails中級チュートリアルは、Ruby on Railsチュートリアル(https://railstutorial.jp/)(Railsチュートリアル)とは著者も対象読者も異なります。
目次
- 1. 序章とセットアップ
- 2. レイアウト
- 3. 投稿
- 4. インスタントメッセージ
- 4-1 非公開チャット
- 4-2 連絡先
- 4-3 グループチャット
- 4-4 メッセンジャー
- 5. 通知
- 5-1 つながりリクエスト
- 5-2 チャット
Rails5「中級」チュートリアル(3-3)投稿機能: テスト(翻訳)
現時点までのアプリにはそこそこ機能が追加されています。たとえわずかな機能であっても、アプリでテストを書いてすべてが正常に機能するための時間を使わなければならない段階に既になっています。アプリの機能が現在の20倍に増えたときのことを想像してみましょう。コードを変更するたびに、すべてが問題なく動作していることを自力でチェックするのは相当なフラストレーションです。これを避けて手動テストの時間を削減するために、テスト自動化を実装することにします。
テストを書き始める前に、ここでテストについて説明します。Railsテスティングガイドも読んでRailsのテスト手法について慣れておくとよいでしょう。
訳注: テストについてCode Climateの以下の記事もどうぞ。
テストに使うもの
フレームワーク: RSpec
私がRailsアプリのテストを書き始めた頃はデフォルトのminitestを使っていましたが、今はRSpecを使っています。私はminitestとRSpecに優劣の違いはないと考えています。どちらも優れたフレームワークであり、私がRSpecを試すことに決めたのは、RailsコミュニティでRSpecの人気が高いと聞いていたからです。今ではほとんどのテストでRSpecを使っています。
サンプルデータ: factory_bot
こちらについても、当初の私はデフォルトのRails wayであるfixtureでサンプルデータを追加してみました。そしてサンプルデータの選択は、テスティングフレームワークを選択するときとは違うことがわかってきました。テスティングフレームワークの選択は、おそらく個人の好みでよいと思いますが、サンプルデータについては違うと思います。fixtureは当初はよかったのですが、アプリが成長するにつれてサンプルデータの制御が難しくなりました。私の選択ミスだったようです。一方factoryは使った当初から何の心配もなく、実に快調です。アプリの規模が大きくても小さくても、サンプルデータを作る手間は変わりません。
訳注: 原文ではfactory_girlでしたが、現在はfactory_botと名前が変わっているので、本記事ではすべてfactory_girl_railsとコードをfactory_bot_rails向けに置き換えました。
受け入れテスト: Capybara
Capybaraはデフォルトでrack_test
ドライバを使いますが、残念ながらこのドライバはJavaScriptをサポートしていません。Capybaraのデフォルトドライバの代わりに、poltergeistを使うことにします。poltergeistはJavaScriptをサポートし、ドライバの設定が私にとって最も簡単でした。
訳注: Rails 5.1ではCapybaraが標準で使えるようになっているほか、「システムテスト」機能が導入されて、RSpec 3.7以降とChromedriverを使ってCapybaraの受け入れテスト(system spec)を楽に設定できるようになっています。今後はシステムテストがRailsの受け入れテストで主流になる可能性もあります。詳しくは以下のTechRacho記事をご覧ください。
テストの対象
私は自分で書いたロジックをすべてテストしています。テスト対象には以下があります。
- ヘルパー
- モデル
- ジョブ
- デザインパターン
- その他自分が書いたロジックすべて
ロジックの他に、Capybaraでアプリの受け入れテストを書いてユーザー操作をシミュレートし、アプリの機能がすべて正常に動作することを確認するようにしています。受け入れテスト(シミュレーションテスト)を補うために、request specを使ってすべてのリクエストが正しいレスポンスを返すことを確認します。
以上のテストは私のニーズに十分合うので、私の個人アプリではこれらをテストしています。当然ですが、テスト方法の標準は人それぞれ、会社ごとに異なることがあります。
コントローラ/ビュー/gemのテストが含まれていない理由がおわかりでしょうか。多くのRails開発者が指摘しているように、コントローラやビューにはロジックを含めるべきではありません。私もこれに賛成です。これらにはテストの必要な部分がそれほどありません。コントローラやビューのテストは、ユーザーシミュレーションテスト(受け入れテスト)で十分効果を得られると私は考えています。gemは既に開発者がテストしていますし、これもシミュレーションテストで十分カバーできると思います。
テスト方法
私ももちろん、可能な限りTDD(テスト駆動開発)アプローチを使うようにしています。TDDでは最初にテストを書き、それからコードを実装します。TDDでは開発フローがよりスムーズになりますが、最終的な機能がどんなふうに見えるか、何が出力されるかが事前にわからないこともあります。コードで実験する場合や、違う実装を試す場合は、テストを最初に書いてから実装する方法だとあまりうまくいきません。
私は何らかのロジックを書く前には必ず(前述のとおり、必ずしもそうではありませんが)、独立したテストすなわち単体テストを書きます。アプリの機能がすべて正常に動作することを確認するために、Capybaraで受け入れテスト(ユーザーシミュレーションテスト)を書きます。
テスト環境のセットアップ
最初のテストを書く前に、テストのための環境を設定しなければなりません。
Gemfile
を開いて以下のgemをtest
グループに追加します。
gem 'rspec-rails', '~> 3.6'
gem 'factory_bot_rails'
gem 'rails-controller-testing'
gem 'headless'
gem 'capybara'
gem 'poltergeist'
gem 'database_cleaner'
再度説明すると、rspec
gemはテスティングフレームワーク、factory_bot
はサンプルデータ追加用、capybara
はアプリのユーザー操作のシミュレーション用、poltergeist
はテストでJavaScriptをサポートするためのドライバです。
訳注: Rails 5.1以降ではCapybaraが標準で使えるのでGemfileでの追加は不要になります。
JavaScriptをサポートする別のドライバを使ってセットアップすることもできます。poltergeist
gemを使う場合はPhantomJSのインストールが別途必要です。インストール方法についてはpoltergeistのドキュメントをご覧ください。
headless
は、ヘッドレスドライバをサポートするのに必要です。poltergeist
はヘッドレスドライバなのでこのgemが必要になります。rails-controller-testing
gemはrequests specを使ってリクエストとレスポンスをテストするときに必要になります。詳しくは後述します。
訳注:
poltergeist
とheadless
の代わりにchromedriver-helper
gemを使うこともできます。インストール方法は以下の記事をご覧ください。
database_cleaner
gemは、テストでJavaScriptを実行した後にtestデータベースをクリーンアップするのに必要です。通常、testデータベースはテストのたびにクリーンアップされますが、JavaScriptを使う機能をテストする場合にデータベースが自動クリーンアップされないことがあります。この動作は今後変更されると思われますが、本チュートリアル執筆時点ではJavaScript実行後にtestデータベースは自動クリーンアップされません。そのため、JavaScriptの各テストの後にtestデータベースが自動クリーンアップされるようテスト環境を手動で構成しなければなりません。database_cleaner
gemが実行されるタイミングについてはこの後設定します。
訳注: 最新のRails 5.1でシステムテストを行う場合、
database_cleaner
gemは不要になりました。
gemの利用目的の説明が終わりましたので、以下を実行してgemをインストールしましょう。
bundle install
以下を実行して、RSpecで使うspec
ディレクトリを初期化します。
rails generate rspec:install
RSpecフレームワークでは、1つのテストを一般に「spec」と呼びます。specを実行するということは、テストを実行するということです。
プロジェクトディレクトリの下にspec
というディレクトリができます。テストは今後ここに書きます。他にtest
というディレクトリがありますが、これはデフォルトのテスト設定の場合にテストを保存する場所です。このディレクトリは今後使わないので、削除して構いませんc(x_X)b。
上述したように、JavaScriptを使うテスト向けにdatabase_cleaner
のセットアップを行わなければなりません。rails_helper.rb
ファイルを開きます。
spec/rails_helper.rb
config.use_transactional_fixtures = true
上の行を以下に変更します。
config.use_transactional_fixtures = false
さらに以下のコードも追加します(Gist)
# spec/rails_helper.rb
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, :js => true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
上のコードスニペットはこちらのチュートリアルのものを使いました。
最後に、もう少し設定を行います。rails_helper.rb
ファイルの設定に以下を追加します(Gist)。
訳注:
require
以外の設定は、RSpec.configure do |config|
ブロックの中に書きます。
# spec/rails_helper.rb
require 'capybara/poltergeist'
require 'factory_bot_rails'
require 'capybara/rspec'
config.include Devise::Test::IntegrationHelpers, type: :feature
config.include FactoryBot::Syntax::Methods
Capybara.javascript_driver = :poltergeist
Capybara.server = :puma
コードについて少し解説します。
require
メソッドによって、追加されたgemのファイルが読み込まれ、以後メソッドが使えるようになります。
config.include Devise::Test::IntegrationHelpers, type: :feature
上の設定は、capybara
テストでdevise
メソッドを使えるようにするためのものです。この設定をどこで知ったかというと、Deviseドキュメントに記載されています。
config.include FactoryBot::Syntax::Methods
上の設定は、factory_bot
gemのメソッドを使うためのものです。この設定もgemのドキュメントで見つけました。
Capybara.javascript_driver = :poltergeist
Capybara.server = :puma
上の2つの設定は、capybara
でJavaScriptをテストできるようにするために必要です。実装方法でわからない点があれば、capybara
に限らず、まずドキュメントをお読みください。
テスト用gemと設定方法のほとんどを、個別ではなく一度に導入した理由は、今後問題が発生したときに備えてテストに必要なものについての全体像を明確にするためです。これで、このセクションに立ち戻ればテストの設定方法はほとんどここで確認できます。gemの導入や設定方法の記述があちこちにあると追うのが大変なのでこのセクションにまとめました。
変更をcommitし、いよいよテストを書くことにしましょう。
git add -A
git commit -m "
Set up the testing environment
- Remove test directory
- Add and configure rspec-rails, factory_bot_rails,
rails-controller-testing, headless, capybara, poltergeist,
database_cleaner gems"
Helper spec
個別のspec(テスト)の種類については、rspecのドキュメントやrspec-rails gemのドキュメントをご覧ください。どちらも内容はだいたい同じですが、多少異なる点もあります。
新しいブランチを切ってそちらに切り替えます。
git checkout -b specs
まだヘルパーメソッドを1つ書いただけなので、これをテストしましょう。
spec
ディレクトリに移動して、helpers
ディレクトリを作成します。
spec/helpers
このディレクトリにnavigation_helper_spec.rb
ファイルを作成します。
spec/helpers/navigation_helper_spec.rb
このファイルに以下のコードを書きます(Gist)。
# spec/helpers/navigation_helper_spec.rb
require 'rails_helper'
RSpec.describe NavigationHelper, :type => :helper do
end
require 'rails_helper'
によって、すべてのテスト設定にアクセスできるようになります。:type => :helper
は、helper spcを扱うことを指定し、これによって特定のメソッドが使えるようになります。
collapsible_links_partial_path
メソッドのテストを書いたnavigation_helper_spec.rb
ファイルは次のようになります(Gist)。
# spec/helpers/navigation_helper_spec.rb
require 'rails_helper'
RSpec.describe NavigationHelper, :type => :helper do
context 'signed in user' do
before(:each) { allow(controller).to receive(:user_signed_in?).and_return(true) }
context '#collapsible_links_partial_path' do
it "returns signed_in_links partial's path" do
expect(helper.collapsible_links_partial_path).to (
eq 'layouts/navigation/collapsible_elements/signed_in_links' )
end
end
end
context 'non-signed in user' do
before(:each) { allow(controller).to receive(:user_signed_in?).and_return(false) }
context '#collapsible_links_partial_path' do
it "returns non_signed_in_links partial's path" do
expect(helper.collapsible_links_partial_path).to (
eq 'layouts/navigation/collapsible_elements/non_signed_in_links' )
end
end
end
end
context
やit
について詳しく学ぶには、RSpecのbasic structureドキュメントをご覧ください。上では「ユーザーがログインしている場合」と「ユーザーがログインしていない場合」の2つのケースについてテストしています。signed in user
コンテキストとnon-signed in user
コンテキストには、それぞれbefore
フックを記述しています。これらのフック(メソッド)は、それぞれのコンテキスト内でテスト実行前に前処理を実行します。ここではテスト実行前にstub
メソッドを前処理として実行し、user_signed_in?
の戻り値をどんな値にもできるようにします(ここではtrue
を返す)。
訳注: rspeck-mocksの
stub
は現在非推奨です。元のコードbefore(:each) { helper.stub
は、stub
でdeprecation warningが表示されるため、allow(controller).to receive
に置き換えました(参考1)(参考2)。
そして最後に、expect
メソッドを用いて、collapsible_links_partial_path
メソッドを呼び出したときの戻り値が期待どおりであることをチェックします。
以下を実行すれば、すべてのテストを実行できます。
rspec spec
navigation_helper_spec.rb
ファイルのテストだけを実行したい場合は、以下を実行します。
rspec spec/helpers/navigation_helper_spec.rb
テストがパスすれば、以下のように表示されます。
変更をcommitします。
git add -A
git commit -m "Add specs to NavigationHelper's collapsible_links_partial_path method"
ファクトリー
次に、テスト実行のためのサンプルデータが必要です。factory_bot
gemを使って、サンプルデータが欲しいときにいつでも簡単に追加でき、ドキュメントも充実しているのでとても快適です。この時点のアプリで作成できるオブジェクトはUser
のみです。userのファクトリーを定義するために、spec
ディレクトリにfactories
ディレクトリを作成します。
spec/factories
factories
ディレクトリの下にusers.rb
ファイルを作成し、以下のコードを追加します(Gist)。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
sequence(:name) { |n| "test#{n}" }
sequence(:email) { |n| "test#{n}@test.com" }
password '123456'
password_confirmation '123456'
end
end
これで、testデータベースでのユーザー作成をspec内でいつでも簡単にfactory_bot
のメソッドで行えるようになりました。ファクトリーの定義方法や使い方の完全なガイドについては、factory_bot
gemのドキュメントをご覧ください。
user
ファクトリーの定義は割りと素直で、user
オブジェクトに持たせる値を定義しています。sequence
メソッドは、ドキュメントを読むとわかるように、User
レコードを追加するたびにn
の値が1つカウントアップします。たとえば最初のユーザー名はtest0
、次のユーザー名はtest1
という具合です。
変更をcommitします。
git add -A
git commit -m "add a users factory"
feature spec
feature specsには、アプリでのユーザー操作をシミュレーションするテストコードを書きます。capybara
gemはfeature specを強化してくれます。
うれしいことに、最初のfeature specを書く準備はもう整っていますので、ログイン/ログアウト/サインアップ機能のテストを書くことにします。
spec
ディレクトリの下にfeatures
ディレクトリを作成します。
spec/features
features
ディレクトリの下に、さらにuser
ディレクトリを作成します。
spec/features/user
user
ディレクトリの下に、login_spec.rb
ファイルを作成します。
spec/features/user/login_spec.rb
ログインテストは次のようになります。
# spec/features/user/login_spec.rb
require "rails_helper"
RSpec.feature "Login", :type => :feature do
let(:user) { create(:user) }
scenario 'ユーザーがloginページにリダイレクトされ、ログインに成功する', js: true do
user
visit root_path
find('nav a', text: 'Login').click
fill_in 'user[email]', with: user.email
fill_in 'user[password]', with: user.password
find('.login-button').click
expect(page).to have_selector('#user-settings')
end
end
このテストコードは、ユーザーがhomeページを開くとloginページに移動するところをシミュレートします。続いてフォームに入力して送信し、最後に#user-settings
要素(ユーザーがログイン中のときだけ現れる)がナビゲーションバーにあることをチェックします。
feature
とscenario
はCapybaraの構文です。feature
はcontext
やdescribe
と同じであり、scenario
はit
と同じ機能です。詳しくはUsing Capybara With Rspecをご覧ください。
let
を使うと、メモ化(memoize)されたメソッドを書いて、そのメソッドが定義されているcontext
の内側にあるすべてのspecで利用できるようになります。
訳注:
let
についてはRSpecのletを使うのはどんなときか?(翻訳)もどうぞ。
ここでは、作成したuser
ファクトリーと、factory_bot
gemのcreate
メソッドも使っています。
js: true
引数を指定すると、JavaScriptに関連する機能をテストできるようになります。
これまでと同様、login_spec.rb
ファイルを指定してテストを実行し、パスすることを確認します。
rspec spec/features/user/login_spec.rb
変更をcommitします。
git add -A
git commit -m "add login feature specs"
これで、ログアウト機能もテストできる状態になりました。user
ディレクトリの下にlogout_spec.rb
ファイルを作成します(Gist)。
spec/features/user/logout_spec.rb
テストの実装は次のようになります。
# spec/features/user/logout_spec.rb
require "rails_helper"
RSpec.feature "Logout", :type => :feature do
let(:user) { create(:user) }
scenario 'ユーザーがログアウトに成功する', js: true do
sign_in user
visit root_path
find('nav #user-settings').click
find('nav a', text: 'Log out').click
expect(page).to have_selector('nav a', text: 'Login')
end
end
このコードは、ユーザーが[Logout]ボタンをクリックするところをシミュレートし、ナビゲーションバーのユーザーのリンクがログインしていない状態になることをチェックします。
sign_in
メソッドはDeviseのヘルパーメソッドです。こうしたヘルパーメソッドは、既にrails_helper.rb
ファイルでinclude
してあります。
テストを実行し、パスすることを確認します。
変更をcommitします。
git add -A
git commit -m "add logout feature specs"
残るは、新しいアカウントでのサインアップ機能です。これをテストしましょう。user
ディレクトリの下にsign_up_spec.rb
ファイルを作成します。テストは次のようになります(Gist)。
# spec/features/user/sign_up_spec.rb
require "rails_helper"
RSpec.feature "Sign up", :type => :feature do
let(:user) { build(:user) }
scenario 'ユーザーがsignupページを開いてサインアップに成功する', js: true do
visit root_path
find('nav a', text: 'Signup').click
fill_in 'user[name]', with: user.name
fill_in 'user[email]', with: user.email
fill_in 'user[password]', with: user.password
fill_in 'user[password_confirmation]', with: user.password_confirmation
find('.sign-up-button').click
expect(page).to have_selector('#user-settings')
end
end
ここではsignupページを開いてフォームへの入力と送信をシミュレートし、#user-settings
要素(ログイン中のユーザーにのみ現れる)が表示されることをチェックします。
create
メソッドではなくDeviseのbuild
メソッドを使うことで、データベースに保存せずに新しいオブジェクトを作成できます。
テストスイート全体を実行し、すべてパスことを確認します。
rspec spec
変更をcommitします。
git add -A
git commit -m "add sign up features specs"
最初のテストができあがりましたので、specs
ブランチをmaster
にmergeします。
git checkout master
git merge specs
specs
ブランチが不要になったので削除します(q__o)。
git branch -D specs
- 前回: Rails5「中級」チュートリアル(3-2)投稿機能: ヘルパー(翻訳)
- 次回: Rails5「中級」チュートリアル(3-4)投稿機能: メインフィード(翻訳)