ユーザー認証のテスト(6) -- スタブを外す
2013/09/09
6回に渡ってユーザー認証機能の基本部分を実装してきました。今回はその仕上げです。
ユーザー認証のテスト(3) -- スタブの活用で、クラスメソッド Customer.authenticate
をスタブ(常に特定の値を返す仮のメソッド)として定義しました。ユーザーインターフェースのためのテストをとりあえず通すためです。
すでに我々は本物の Customer.authenticate
メソッドを手に入れたので、今やスタブを外す時です。
スタブを外す
現状のテストコード spec/features/login_and_logout_spec.rb
は次のようになっています:
require 'spec_helper' describe 'ログイン' do specify 'ユーザー認証成功' do Customer.stub(:authenticate).and_return(FactoryGirl.create(:customer)) visit root_path within('form#new_session') do fill_in 'username', with: 'taro' fill_in 'password', with: 'correct_password' click_button 'ログイン' end expect(page).not_to have_css('form#new_session') end specify 'ユーザー認証失敗' do Customer.stub(:authenticate) visit root_path within('form#new_session') do fill_in 'username', with: 'taro' fill_in 'password', with: 'wrong_password' click_button 'ログイン' end expect(page).to have_css('p.alert', text: 'ユーザー名またはパスワードが正しくありません。') expect(page).to have_css('form#new_session') end end
これを次のように変更します:
require 'spec_helper' describe 'ログイン' do before { create(:customer, username: 'taro', password: 'correct_password') } specify 'ユーザー認証成功' do visit root_path within('form#new_session') do fill_in 'username', with: 'taro' fill_in 'password', with: 'correct_password' click_button 'ログイン' end expect(page).not_to have_css('form#new_session') end specify 'ユーザー認証失敗' do visit root_path within('form#new_session') do fill_in 'username', with: 'taro' fill_in 'password', with: 'wrong_password' click_button 'ログイン' end expect(page).to have_css('p.alert', text: 'ユーザー名またはパスワードが正しくありません。') expect(page).to have_css('form#new_session') end end
3行目が追加され、各エグザンプルの1行目にあった Customer.stub
メソッドの呼び出しが削除されています。
追加された before
メソッドは、エグザンプルグループ内のすべてのエグザンプル共通の事前作業を定義します。各エグザンプルが実行される直前に before
ブロック内のコードが毎回実行されるのです。ここでは、特定のユーザー名とパスワードを持つ顧客のレコードをデータベースに投入しています。
テストが通ることを確認してください。
$ bin/rspec spec/features/login_and_logout_spec.rb .. Finished in 0.63329 seconds 2 examples, 0 failures Randomized with seed 60662
うまく行きましたね!
ここまでのおさらい
ユーザー認証機能の開発はまだ続きますが、ここまでの流れをおさらいしておきましょう。
こんな風に進みましたね:
- ユーザーインターフェースのテストを作り、テストを通す。
spec/features/login_and_logout_spec.rb
を作成。- ログインフォームのHTMLテンプレートを作成。
POST /login
のルーティングを設定。sessions#create
アクションを仮実装(単にトップページにリダイレクト)。sessions#create
アクションの実装を進める(ログイン失敗メッセージの表示)。sessions#create
アクションの実装を進める(未実装のCustomer.authenticate
メソッドを呼ぶ)。Customer.authenticate
メソッドをスタブ化し、テストを通す。
Customer.authenticate
メソッドのテストを作り、テストを通す。spec/models/customer_spec.rb
に、エグザンプルグループ.authenticate
を追加。- ユーザー認証成功の場合についてエグザンプルを書き、テストを通す。
customers
テーブルにusername
カラムを追加。Customer
モデルにpassword
属性を追加。Customer.authenticate
メソッドの仮実装(ユーザー名だけで認証)。
- ユーザー認証失敗の場合についてエグザンプルを書き、テストを通す。
customers
テーブルにpassword_digest
カラムを追加。Customer.authenticate
メソッドの実装を進める(パスワードを平文で保存)。
Customer#password=
メソッドのテストを作り、テストを通す。spec/models/customer_spec.rb
に、エグザンプルグループ#password=
を追加。- パスワード情報の保存に関するエグザンプルを書く。
bcrypt-ruby
を導入し、テストを通す。
Customer.authenticate
メソッドに関するエグザンプルを追加し、テストを通す。- ユーザーインターフェースのテストからスタブを外す
注目すべきは「仮実装」と「スタブ」という言葉です。どちらもテストを通すのに必要最小限のコードを書くことを意味します。エグザンプルの追加とメソッドの仮実装/スタブ化を繰り返しながら、最終的には複雑な機能を持つクラス/メソッドを完成させています。
Outside-In の原則が貫かれていることにも注目しましょう。まず、最も外側のユーザーインターフェースから実装を始めています(1)。次に、ユーザー認証機能の本体である Customer.authenticate
メソッドの実装に移ります(2)。そして、パスワード情報の保存という最も内側の機能を作ります(3)。ここに至り、逆の順路をたどって外側に戻っていきます。まず、Customer.authenticate
メソッドの仕上げをします(4)。最後にユーザーインターフェースのテストからスタブを外します(5)。もはや「仮実装」や「スタブ」の状態で残っているメソッドは存在しません。アプリケーションとして動くことが確かめられたのです。
つまり、Outside-In というのは単に「外側から内側へ」と実装を進めるということではありません。「外側から内側へ、そして再び外側へ」という開発の流れを表現する言葉なのです。
次回は
次回からは、ユーザー認証機能の付加的な仕様である「ログインポイントの付与」のテストと実装を行っていきます。では、また。