以前、受入テストは RSpec とは別に Cucumber というアプリケーションを使っていた。最近は、Turnip と Capybara を用いて、RSpec 内で実施できる受入テストが使われている。今回は、年度に関するテストで受入テストを実施してみる。
turnip の設定
すでに Gemfile には登録してあるので、その後の設定を行う。まず、.rspec に以下の行を追加する。.rspec は .gitignore に入れてあるので、各マシンで設定を追加しておく。Jenkins の設定も忘れないこと。
-r turnip/rspec
次に、spec/features, spec/steps のフォルダを作成しておく。
$ mkdir -p spec/acceptance/{features,steps}
spec/acceptance/steps の読み込みを行うために、spec/turnip_helper.rb を作成する。また、最後の二行は Capybara でログイン処理をスキップするため(login_as を呼び出すため)に必要なものである。
Dir.glob("spec/acceptance/steps/*steps.rb") { |f| load f, true } include Warden::Test::Helpers Warden.test_mode!
feature の記述
さっそく、spec/acceptance/features/kyoumu_years.feature を書いてみる。本システムではリンクをわかりやすくするためにすべてを括っている。ただし、 が入っていると turnip がプレースホルダーを認識しないので、”” で括っておく。ここでは途中までをテストする。続きは後日。
# language: ja 機能: 年度の追加、変更 シナリオ: 教務室のトップページから年度変更に飛び、新規年度を作成し、年度を変更する 前提 教務室でログインする ならば "[年度変更]"と表示されていること もし "[年度変更]"リンクをクリックする ならば 画面に年度一覧と表示されていること
Guard でリターンキーを押すと次のように表示される。「教務室でログインする」という step がないので PENDING になっている。
年度の追加、変更 教務室のトップページから年度変更に飛び、新規年度を作成し、年度を変更する 前提教務室でログインする -> ならば”[年度変更]”と表示されていること -> もし”[年度変更]”リンクをクリックする -> ならば年度一覧と表示されていること (PENDING: No such step: '教務室でログインする')
認証のスキップ
このテストを通過させるために、「教務室でログインする」機能を step として記述する。今後いくつかの権限のものを追加する予定なので、プレイスホルダーを使って切り替えられるようにしておく。spec/acceptance/steps/login_steps.rb は以下の記述した。
USERS = [ ['教務室', :user_skyoumu ], ['教員', :user_hkob ], ] step %(:jnameでログインする) do |jname| login_as(FactoryGirl.create(USERS.assoc(jname).last), scope: :user) visit root_path end
RSpec で使用した login_user_as は受入テストでは使用できないので、login_as を使用している。また、jname は日本語で与えられるので、USERS 配列(後で増やす予定)から assoc で FactoryGirl のキーを取り出している。
step を保存すると Guard が起動し、以下の様なエラーとなる。
1) 年度の追加、変更 教務室のトップページから年度変更に飛び、新規年度を作成し、年度を変更する 前提教務室でログインする -> ならば”[年度変更]”と表示されていること -> もし”[年度変更]”リンクをクリックする -> ならば年度一覧と表示されていること Failure/Error: 前提 教務室でログインする ArgumentError: Factory not registered: user_skyoumu
これは user_skyoumu という Factory がないということなので、spec/factories/user.rb に前回同様継承ファクトリで作成する。
factory :user_skyoumu do name 'skyoumu' end
この結果、認証はスキップされ、welcome#index に飛ぶことができた。
Failure/Error: ならば “[年度変更]”と表示されていること expected to find text “[年度変更]” in "Welcome#index Find me in app/views/welcome/index.html.haml ログアウト"
権限の確認 (User モデル)
教務室でログインした場合、welcome#index から教務室用のトップページにリダイレクトして欲しい。旧システムではコントローラでかなり頑張ってしまったので、ユーザ側で判断をするように変更したい。feature のテストの途中だが、welcome_controller の spec を修正する。後で別の権限のテストも行うので、user_skyoumu 用の context を用意し、teacher_kyoumus_path へのリダイレクト処理に変更している。旧システムより教職員用サーバの URL はすべて teacher/ 以下の namespace を利用している。後で学生がこの URL にはアクセスできないように権限設定をするためである。
require 'rails_helper' RSpec.describe WelcomeController, :type => :controller do describe "GET index" do context "for :user_skyoumu" do it do login_user_as :user_skyoumu get :index expect(response).to redirect_to(teacher_kyoumus_path) end end end end
Guard の画面には以下のようにエラーが表示される。
1) WelcomeController GET index for :user_skyoumu Failure/Error: expect(response).to redirect_to(teacher_kyoumus_path) NameError: undefined local variable or method `teacher_kyoumus_path' for #<RSpec::ExampleGroups::WelcomeController::GETIndex::ForUserSkyoumu:0x007fec099ceee0>
teacher_kyoumus_path のための generator を実行する。
$ bin/rails g controller teacher/kyoumus index create app/controllers/teacher/kyoumus_controller.rb route namespace :teacher do get 'kyoumus/index' end invoke haml create app/views/teacher/kyoumus create app/views/teacher/kyoumus/index.html.haml invoke rspec create spec/controllers/teacher/kyoumus_controller_spec.rb invoke assets invoke coffee invoke scss
config/routes.rb を見ると以下のように追加されている。
namespace :teacher do get 'kyoumus/index' end
このままだと teacher_kyoumus_index_path になってしまうので、resource に修正する。
namespace :teacher do resources :kyoumus, only: [ :index ] end
この結果、Guard 画面は redirect されていないというエラーに変わる。
2) WelcomeController GET index for :user_skyoumu should redirect to "/teacher/kyoumus" Failure/Error: expect(response).to redirect_to(teacher_kyoumus_path) Expected response to be a <redirect>, but was <200> # ./spec/controllers/welcome_controller_spec.rb:9:in `block (4 levels) in <top (required)>'
このリダイレクトを実現するために、app/controllers/welcome_controller.rb の index メソッドを以下のように修正する。user でログインしているときには、current_user に user_session を渡して、権限を設定してもらう。その後、権限を確認するメソッド is_kyoumu? を用いてリダイレクトを行う(この部分も後日増える予定)。
def index if user_signed_in? current_user.set_privileges(user_session) redirect_to teacher_kyoumus_path if is_kyoumu? end end
set_privileges メソッドがないと言われる。
1) WelcomeController GET index for :user_skyoumu Failure/Error: get :index NoMethodError: undefined method `set_privileges' for #<User:0x007fcf311fee30>
set_privileges を作成するため、先にテストを記述する。spec/models/user_spec.rb を以下のように記述した。%i はシンボルの配列を作成するもので、Ruby 2.0 からの新しい記述法である。また、values_at は以前は indexes と呼ばれていたもの。session ハッシュの User.Privileges に対応する値を一括で取り出し、answers と比較する。User.Privileges 配列を引数として展開するため * を使っている。ユーザや権限はこれから増えてくるのでそれに耐えうるテストにしている。
RSpec.describe User, :type => :model do context "Each user of users" do user_keys = %i( user_hkob user_skyoumu ) context "checking the privileges" do answers = { user_hkob: [ false ], user_skyoumu: [ true ], } user_keys.each do |user_key| user = FactoryGirl.build(user_key) session = {} user.set_privileges(session) it { expect(session.values_at(*User.Privileges)).to eq(answers[user_key]) } end end end
この結果「undefined method `set_privileges' for #
app/models/user.rb に set_privileges を記述する。また、Privileges 定数も定義しておく。Privileges 配列に対応するメソッドを呼び出し、true であった場合にハッシュに登録する。Privileges も当初 %i で作成していたが、session にはシンボルで代入しても取り出すときには文字列になってしまうことがわかり、後から %w に修正した。
# 権限一覧 Privileges = %w( is_kyoumu? ) # @param [Hash] session ハッシュ # @return [Hash] 更新された session ハッシュ def set_privileges(session) Privileges.each do |p| session[p] = true if self.send(p) end session end
こんどは、「undefined method `is_kyoumu?' for #
def is_kyoumu? %w( skyoumu akyoumu ).include?(name) end
この結果、set_privileges のテストは以下のように通過する。
User Each user of users checking the privileges should eq [nil] should eq [true]
ただし、仕様のメッセージが悲しいので、it に文章を記述する。
it "#{user_key}'s privileges for #{User::Privileges} should eq #{answers[user_key]}" do expect(session.values_at(*User::Privileges)).to eq(answers[user_key]) end
この結果、RSpec の表示は以下のようになる。
User Each user of users checking the privileges user_hkob's privileges for [“is_kyoumu?”] should eq [nil] user_skyoumu's privileges for [“is_kyoumu?”] should eq [true]
モデルのテストが通過したので、Guard でリターンキーを押してみる。モデルの実装が終わったので、今度は feature のテストが進んでいる。
1) 年度の追加、変更 教務室のトップページから年度変更に飛び、新規年度を作成し、年度を変更する 前提教務室でログインする -> ならば”[年度変更]”と表示されていること -> もし”[年度変更]”リンクをクリックする -> ならば年度一覧と表示されていること Failure/Error: 前提 教務室でログインする NoMethodError: undefined method `is_kyoumu?' for #<WelcomeController:0x007fea92039df8>
これは、controller の is_kyoumu? がないというエラーである。これはすべての controller から呼ばれるため、app/controllers/applications_controller.rb に記載する。admin でも同様の権限判断が入る予定なので、user_session だけでなく admin_session も確認するようにしている。セッションキーを確認する each_session は引数がある場合は、その引数の値を返し、ない場合には session 自体を返す。
# @param [Symbol] key 確認する session キー(省略可) # @return [Hash] key がないとき admin_session か user_session # @return [Object] key があるとき admin_session か user_session のどちらかの session キーの内容 def each_session(key = nil) ans = user_session || admin_session ans = ans && ans[key] if key ans end # @return [Boolean] session[is_kyoumu?] を返す(@is_kyoumu にキャッシュする) def is_kyoumu? @is_kyoumu ||= each_session('is_kyoumu?') end
この結果、feature の結果は以下のようになった。正しく kyoumus/index にリダイレクトされるようになった。
1) 年度の追加、変更 教務室のトップページから年度変更に飛び、新規年度を作成し、年度を変更する 前提教務室でログインする -> ならば”[年度変更]”と表示されていること -> もし”[年度変更]”をクリックする -> ならば年度一覧と表示されていること Failure/Error: ならば “[年度変更]”と表示されていること expected to find text “[年度変更]” in "Teacher::Kyoumus#index Find me in app/views/teacher/kyoumus/index.html.haml"
かなり長くなったので、本日はここまで。
written by iHatenaSync