NAiZのプログラミング道日記

個人web開発勉強で学んだことや経験を備忘録形式で呟きます。

Ruby on Rails チュートリアル x日目(1章~9章)

Railsチュートリアルの9.3.1以降について

久しぶりの投稿になります。
今日で、railsチュートリアルを9章まで終えることができました。

実は、一度10章までやったのですが、段々とわからないことが多くなり、遂には現実逃避の読書に浸ってしまう次第に。(おかげで3冊も400ページくらいの本を読みましたが。)
しかし、これはまずいなと感じ、コンピュータの歴史を調べたりして無理やりモチベーションを上げて、もう一度じっくり復習する時間を取り、RubyCSS、Gitの勉強をやってから1~9章の2週目を行いました。

9章を終えたのですが、ここで疑問が。
疑問①
『次に統合テストでも同様のヘルパーを実装していきます。ただし統合テストではsessionを直接取り扱うことができないので、代わりにSessionsリソースに対してpostを送信することで代用します (リスト 8.23)。メソッド名は単体テストと同じ、log_in_asメソッドとします。』とあるが、なぜsessionを直接取り扱うことができないのか。

疑問②
『また、リスト 9.24で定義したlog_in_asヘルパーメソッドでは、session[:user_id]と定義してしまっています。このままでは、current_userメソッドが抱えている複雑な分岐処理を統合テストでチェックすることが非常に困難です。ただありがたいことに、Sessionsヘルパーのテストでcurrent_userを直接テストすれば、この制約を突破することができます。』
とあるが、なぜか?

の二つです。

まず最初に…
“9.3.1 [Remember me] ボックスをテストする“で何をやっているのかを少しまとめます。

Railsチュートリアルに書いてある通りなのですが、ユーザが記憶されるにはログインが必要です。リスト 8.23ではpostメソッドと有効なsessionハッシュを使ってログインしています。
リスト 8.23: 有効な情報を使ってユーザーログインをテストする
test/integration/users_login_test.rb

require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "login with valid information" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
  end
end

だけど毎回書くのは面倒、使いまわせるようにlog_in_asなるヘルパーメソッドをいっちょ作りましょう。ということで以下
『ログイン済みのユーザーをテストする方法はいくつかありますが、今回はコントローラの単体テストを使っていきましょう。具体的には、sessionメソッドを直接操作して、:user_idキーにuser.idの値を代入してみます (これはリスト 8.14の方法と同じです)。』
とあります!
思い出すんだ…リスト 8.14…。
『同じログイン手法を様々な場所で使い回せるようにするために、Sessionsヘルパーにlog_inという名前のメソッドを定義することにします (リスト 8.14)。』
リスト 8.14: log_inメソッド
app/helpers/sessions_helper.rb

module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
end

確かこれでユーザーのブラウザ内の一時cookiesに暗号化済みのユーザーIDが自動で作成されるんですよね。

もどりまして
リスト 9.24: log_in_asヘルパーを追加する
test/test_helper.rb

ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
  fixtures :all

  # テストユーザーがログイン中の場合にtrueを返す
  def is_logged_in?
    !session[:user_id].nil?
  end

  # テストユーザーとしてログインする
  def log_in_as(user)
    session[:user_id] = user.id
  end
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

特徴①メソッド名の違い
前回のlog_inメソッド(8章)はテストじゃなく、ログインのためのメソッド、今回は[Remember me] ボックスをテストする用のなのでlog_in_asメソッド
と区別しているので名前が違います!

特徴②同じtest/test_helper.rbファイルにまとめている!(classが二つ)
これはまあ、上はおまけらしいですが(現時点で)、重要なのは我々人間側からみて同じ名前であるということ。
ログイン済のテストのときは脳死でlog_in_asとソースに記述して呼び出せばいいんだよ。のことらしいです。
名前が同じでも、クラスが違うから適材適所に呼び出されるって感じなのかな。
で、ここからが本題。
疑問だったことは(再掲)
疑問①
『次に統合テストでも同様のヘルパーを実装していきます。ただし統合テストではsessionを直接取り扱うことができないので、代わりにSessionsリソースに対してpostを送信することで代用します (リスト 8.23)。メソッド名は単体テストと同じ、log_in_asメソッドとします。』とあるが、なぜsessionを直接取り扱うことができないのか。

疑問②『また、リスト 9.24で定義したlog_in_asヘルパーメソッドでは、session[:user_id]と定義してしまっています。このままでは、current_userメソッドが抱えている複雑な分岐処理を統合テストでチェックすることが非常に困難です。ただありがたいことに、Sessionsヘルパーのテストでcurrent_userを直接テストすれば、この制約を突破することができます。』
とあるが、なぜか?

です。
順番にみていきます。
まず①では2つのlog_in_asについてみます。
class ActiveSupport::TestCase は単体テストです。
class ActionDispatch::IntegrationTest は統合テストです。(Integration:統合)

単体テストではsessionメソッドを直接操作しています。これはオッケーです。
しかし下の方は、統合テストなのでメソッドは直接操作できません。なぜなのか。
答えはrailsの創造主様がそう作ったからだそうです。創造主であるデイヴィッド・ハイネマイヤー・ハンソン曰く『セッションは、コントローラの内部構造です。統合テストは、コントローラテストよりもブラックボックスです。このようにコントローラの内部をテストする必要はないと思います。代わりに、UIに表示されるものをテストするようにテストを作り直します。』と仰っています。
なるほど。よくわからんw. ⇒(https://github.com/rails/rails/issues/23386)

そして②の疑問ですが、
そもそもテストしたい箇所はsession[:user_id]がnilのときです!勝手に定義されてしまっていてはテストができない!ということ。
だからsessionヘルパーのテストで直接テストしているということになります。
(このときの@user=users(:michael)はfixtureのテストデータを用いている)

感想
9章めっちゃ難しかったです。正直、6割くらいしか理解できていないと思います。また挫折しそうですが、上記の疑問をいつか解決して記事で発信できるように頑張りたいと思います。(笑)