はじめに
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版の 9章 9.3 [Remember me] のテストの演習まとめ&解答例です。
個人の解答例なので、誤りがあればご指摘ください。
動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1
9.3.1 [Remember me] ボックスをテストする
本章での学び
単体テストでログインできるようにする
テスト内でユーザがログインできるヘルパーメソッドを定義する。
def log_in_as(user) session[:user_id] = user.id end
結合テストでログインできるようにする
結合テスト内でもテストユーザとしてログインできるヘルパーメソッドを定義する。
class ActionDispatch::IntegrationTest # テストユーザとしてログインする def log_in_as(user, password: 'password', remember_me: '1') post login_path, params: { session: { email: user.email, password: password, remember_me: remember_me } } end end
テストケースの作成
ユーザのログインのテストケースに2つのケース追加する。 以下、注意点。
- テストケースの中で、
cookies[:user_id]
のように、cookiesメソッドにシンボルは使えない cookies['remember_token']
のように、文字列シンボルなら使える
テストケース1
- テストユーザとしてログインする(ユーザ情報は記憶する)
remember_token
がnilではないことを確認する
テストケース2
- テストユーザとしてログインする(ユーザ情報は記憶しない)
remember_token
がnilであることを確認する
演習1
リスト 9.25の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使用します。このメソッドにはインスタンス変数に対応するシンボルを渡します。 たとえば、createアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。 本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userを (インスタンス変数ではない) 通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。 このアイディアに従ってリスト 9.27とリスト 9.28の不足分を埋め (ヒントとして?やFILL_INを目印に置いてあります)、[remember me] チェックボックスのテストを改良してみてください。
create
アクション内で、ローカル変数user
をインスタンス変数@user
に書き換える。
def create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) #ユーザログイン後にユーザ情報のページヘリダイレクトする log_in @user #チェックされていたらユーザを記憶する params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) redirect_to @user else
統合テストケース内で、assigns(:user)
を追加する。
assert_equal cookies['remember_token'], assigns(:user).remember_token
テストを実行し、greenになることを確認。
yokoyan:~/workspace/sample_app (advanced-login) $ rails test Running via Spring preloader in process 1914 Started with run options --seed 28794 26/26: [==============================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.09513s 26 tests, 71 assertions, 0 failures, 0 errors, 0 skips
9.3.2 [Remember me] をテストする
本章での学び
テストコードに例外処理を埋め込む
current_userヘルパーメソッド内をテストしていないため、わざと例外処理を入れる。 例外処理を入れたにも関わらず、テストをパスしたら、該当部分がテストされていないことがわかる。 テストを忘れている疑いがあるコードをテストする際に有効なテクニック。
例外処理を意図的に発生させるためには、raiseを使う。
自身が表すスレッドで強制的に例外を発生させます。
# 現在のログイン中のユーザーを返す(いる場合) def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) raise # テストがパスすれば、この部分がテストされていないことがわかる user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) # ログイン処理 log_in(user) @current_user = user end end end
テストを実行すると、greenとなる。(例外が発生しない)
yokoyan:~/workspace/sample_app (advanced-login) $ rails test Running via Spring preloader in process 1737 Started with run options --seed 39993 26/26: [=================================================================================================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.34949s 26 tests, 71 assertions, 0 failures, 0 errors, 0 skips
ヘルパーメソッドのテスト
以下の3点を確認するテストを作成する。
1.fixtureでuser
変数を定義する
@user = users(:michael)
2.渡されたユーザをremember
メソッドで定義する
remember(@user)
3.current_user
が渡されたユーザと同じであることを確認する。また、ログイン状態かどうかを確認する。
# assert_equal 期待値, 実際の値 assert_equal @user, current_user assert is_logged_in?
テストを実行すると、redになる。(raiseで例外が発生している)
yokoyan:~/workspace/sample_app (advanced-login) $ rails test test/helpers/sessions_helper_test.rb Running via Spring preloader in process 2674 Started with run options --seed 41311 ERROR["test_current_user_returns_nil_when_remember_digest_is_wrong", SessionsHelperTest, 0.05904175399336964] test_current_user_returns_nil_when_remember_digest_is_wrong#SessionsHelperTest (0.06s) RuntimeError: RuntimeError: app/helpers/sessions_helper.rb:23:in `current_user' test/helpers/sessions_helper_test.rb:17:in `block in <class:SessionsHelperTest>' ERROR["test_current_user_returns_right_user_when_session_is_nil", SessionsHelperTest, 0.06918600399512798] test_current_user_returns_right_user_when_session_is_nil#SessionsHelperTest (0.07s) RuntimeError: RuntimeError: app/helpers/sessions_helper.rb:23:in `current_user' test/helpers/sessions_helper_test.rb:11:in `block in <class:SessionsHelperTest>' 2/2: [===================================================================================================================================================================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.07530s 2 tests, 0 assertions, 0 failures, 2 errors, 0 skips
raiseを外してテストを再実行すると、greenになることを確認。
yokoyan:~/workspace/sample_app (advanced-login) $ rails test test/helpers/sessions_helper_test.rb Running via Spring preloader in process 1608 Started with run options --seed 48069 2/2: [================================================================================================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.07859s 2 tests, 3 assertions, 0 failures, 0 errors, 0 skips
演習1
リスト 9.33にあるauthenticated?の式を削除すると、リスト 9.31の2つ目のテストで失敗することを確かめてみましょう (このテストが正しい対象をテストしていることを確認してみましょう)。
該当箇所をコメントアウトする。
# 現在のログイン中のユーザーを返す(いる場合) def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user # if user && user.authenticated?(cookies[:remember_token])
テストを実行すると、redになることを確認。
yokoyan:~/workspace/sample_app (advanced-login) $ rails test test/helpers/sessions_helper_test.rb Running via Spring preloader in process 1663 Started with run options --seed 43091 FAIL["test_current_user_returns_nil_when_remember_digest_is_wrong", SessionsHelperTest, 0.06601983698783442] test_current_user_returns_nil_when_remember_digest_is_wrong#SessionsHelperTest (0.07s) Expected #<User id: 762146111, name: "Michael Example", email: "michael@example.com", created_at: "2017-05-29 21:37:58", updated_at: "2017-05-29 21:37:58", password_digest: "$2a$04$JRr0GEmdxOPGIOMV0.MXr.RZc8lyrDK8lgPqBfbNHKB...", remember_digest: "$2a$04$4OGtkkjBH4SzRn8t.yaEZ.vMue2zpuqL9zx3FlFhCip..."> to be nil. test/helpers/sessions_helper_test.rb:17:in `block in <class:SessionsHelperTest>' 2/2: [================================================================================================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.07688s 2 tests, 3 assertions, 1 failures, 0 errors, 0 skips
メンテナンスモードを使ったherokuへのデプロイ
以下のコマンドでherokuで動く本番環境をメンテナンスモードにできる。
$ heroku maintenance:on
メンテナンスモードに切り替わったら、herokuへデプロイする。
$ git push heroku $ heroku run rails db:migrate
デプロイが完了したら、メンテナンスモードをオフにする。
$ heroku maintenance:off
おわりに
ロジックが複雑になってきた分、テストケースも難しくなってきました。 単体テストだけでなく、結合テストもコード化しているのは新鮮です。 (ぜひうちの会社でもやりたい・・・)