紙一重の積み重ね

35歳のエンジニアがなれる最高の自分を目指して、学んだことをこつこつ情報発信するブログです。

【9章】Ruby on Railsチュートリアル演習まとめ&解答例【9.3 [Remember me] のテスト】

はじめに

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

image

メンテナンスモードに切り替わったら、herokuへデプロイする。

$ git push heroku
$ heroku run rails db:migrate

デプロイが完了したら、メンテナンスモードをオフにする。

$ heroku maintenance:off

おわりに

ロジックが複雑になってきた分、テストケースも難しくなってきました。 単体テストだけでなく、結合テストもコード化しているのは新鮮です。 (ぜひうちの会社でもやりたい・・・)