使用Cucumber+Rspec玩转BDD(2)——邮件激活
                                                            生活随笔
收集整理的這篇文章主要介紹了
                                使用Cucumber+Rspec玩转BDD(2)——邮件激活
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.                        
                                使用Cucumber+Rspec玩轉(zhuǎn)BDD(2)——郵件激活
2009年3月2日 星期一
### 溫故知新 ###
??? 前面我們已經(jīng)完成了新用戶注冊(cè)功能的開發(fā),為了方便我們后面的開發(fā)工作且不擾亂之前的工作成果,我們先將這份源代碼歸檔并做個(gè)標(biāo)記。
??? 為了獲得更好的閱讀體驗(yàn),讀者朋友們可以在這里下載源碼:http://github.com/404/bdd_user_demo/tree/master
### 提交工作成果到GIT倉庫 ###
??? $ cd ~/code/user_demo
??? $ git init
??? $ git add .
??? $ git commit -m?"A user can be able to sign up."
??? $ git tag v1
??? “git init” 會(huì)在 ~/code/user_demo 目錄中初始化版本庫;接著 “git add .” 將 user_demo 目錄中的所有文件信息編入索引(index files);然后 “git commit” 命令將根據(jù) index 中的信息將工作內(nèi)容提交到項(xiàng)目的GIT倉庫里邊去,-m 選項(xiàng)加上了本次提交的一些說明;最后 “git tag” 給這次提交所生成的版本號(hào)標(biāo)記了一個(gè)別名叫 v1。
??? 其實(shí)好習(xí)慣是在新建rails-app后就初始化版本庫。由于篇幅的關(guān)系,筆者才將些許GIT的內(nèi)容放到這篇文章中。這不,正好派上用場(chǎng)嘍!
??? 在主干(master)上工作是危險(xiǎn)的,因?yàn)榭刂频牟粔蚝脮?huì)擾亂版本,這不是我們?cè)敢饪吹降摹榇?#xff0c;GIT允許我們?cè)谥鞲傻赖幕A(chǔ)上建立新的分支(branch),在分支中進(jìn)行開發(fā)工作,這樣好控制風(fēng)險(xiǎn)。比如有時(shí)候分支中的工作搞得一塌糊涂,開發(fā)人員想重來的時(shí)候,直接丟掉刪除這個(gè)分支再新建一個(gè)工作分支重新工作就是了,這對(duì)項(xiàng)目中的主干完全沒有絲毫影響(不會(huì)擾亂你上次提交到master中的工作成果),等你在新分支中開發(fā)完畢后,再將這個(gè)分支中的工作成果歸并到主干中就行。GIT的分支告訴我們,丟掉一個(gè)爛攤子比收拾一個(gè)爛攤子要輕松得多;潛意識(shí)里,我們幾乎一致認(rèn)為這對(duì)開發(fā)人員的大腦是友好的!:)
??? 下面我們?cè)谥鞲傻幕A(chǔ)上為后面郵件激活這個(gè)功能的開發(fā)新建一個(gè)分支。
### 新建工作分支 ###
????$ git checkout -b email_activation
??? 或者,
????$ git branch email_activation
????$ git checkout email_activation
??? 查看當(dāng)前工作所在的分支,
????$ git branch
??? 會(huì)返回項(xiàng)目中的所有分支,前面加星*就是當(dāng)前的工作分支。
??? 做開發(fā)要步步為營(yíng),不是嗎?git可以很方便地幫我們做到這點(diǎn)。在歸檔源碼后,接著我們新建了一個(gè)名為 email_activation 的分支,并將當(dāng)前的工作狀態(tài)從master主干切換到email_activation分支中。這里說明下,此時(shí)的 email_activation 相當(dāng)于之前源碼(v1)的一份副本,這份副本是我們進(jìn)行后續(xù)開發(fā)的基礎(chǔ);后面我們將在用戶已經(jīng)能夠注冊(cè)的基礎(chǔ)上進(jìn)行用戶激活帳號(hào)的開發(fā)工作,只不過在這個(gè)基礎(chǔ)上所開發(fā)的一舉一動(dòng)都會(huì)被記錄到email_activation分支中。當(dāng)用戶注冊(cè)成功并能通過郵件激活帳號(hào)后,我們就可以將email_activation分支下的工作成果提交且歸并到master主干中,從而把郵件激活的功能和用戶注冊(cè)的功能完美的銜接在一起,同時(shí)使得項(xiàng)目的版本干凈整潔。
??? 如果我們?cè)趀mail_activation的分支中的開發(fā)工作不盡人意,怎么辦呢?如果是一些小小的修改,那非常好辦,直接改成你想要的就是了;可如果是大范圍地修改后,結(jié)果卻不是你想要的,有時(shí)會(huì)萌發(fā)重做的想法。下面就來告訴你一些開倒車的技巧:
??? 如果新增了文件,需要先用git add添加(這會(huì)被編入git的index,但不會(huì)提交到git倉庫),否則回滾后會(huì)遺留下來(這句話好像就是說,等到你重新開發(fā)的時(shí)候發(fā)現(xiàn)要編碼的文件已經(jīng)存在了)。可以用 git status 命令查看都添加或者修改了哪些文件。
??? 如果你當(dāng)前的工作目錄(working tree)已經(jīng)混亂不堪,但是還沒有提交,可以使用:
????$ git reset --hard
??? 這會(huì)丟棄所有的改變,包括去除已經(jīng)加到git index里邊的內(nèi)容;然后將 working tree 和 index 恢復(fù)到上次commit時(shí)的狀態(tài)。
??? 如果想回滾到一個(gè)指定的版本,就需要指定版本號(hào):
????$ git reset --hard v1
??? v1 是我們?cè)谥皹?biāo)記過的別名,即上次commit所生產(chǎn)的版本號(hào)別名,也可以替換成commit后的版本號(hào),比如 af2d45c... ,版本號(hào)是一個(gè)唯一的哈希值,每次commit都會(huì)生成一個(gè),省去了你找不到版本號(hào)的尷尬;基本上,使用git log 命令都能看到版本號(hào)。指定版本號(hào)的時(shí)候不需要寫上所有字符,取前5個(gè)就可以,反正能說明版本號(hào)是唯一的就行了。比如你只有兩次提交記錄,指定版本號(hào)的時(shí)候取哈希值的前兩個(gè)字符又何嘗不可呢?
??? 還有,記得 --hard 選項(xiàng)要慎重使用,具體的您可以使用 “git reset -h” 命令查閱更多關(guān)于撤銷修改的詳細(xì)信息。
??? 如果只是想放棄對(duì)某一文件的修改,可以使用 checkout 命令。這個(gè)命令不單用于分支間的切換,還可以回滾一個(gè)指定的文件內(nèi)容到上次所做的修改,例如:
????$ git checkout app/models/user.rb
??? 這會(huì)放棄對(duì)user.rb所做的修改,并將user.rb的內(nèi)容從上一個(gè)已提交的版本中更新回來。當(dāng)然還可以指定回滾到指定版本,例如:
????$ git checkout v1 app/models/user.rb
??? 這會(huì)將user.rb的內(nèi)容從已提交的v1所對(duì)應(yīng)的版本中更新回來。
??? 好了,到此您已經(jīng)了解了一些實(shí)用的GIT知識(shí);是時(shí)候步入正題進(jìn)行我們的開發(fā)工作了,我們來了解下工作內(nèi)容。
###?郵件激活功能?###
??? 1. 用戶成功注冊(cè)成為網(wǎng)站用戶;
??? 2. 系統(tǒng)發(fā)送一封包含激活鏈接的郵件到用戶注冊(cè)時(shí)填寫的郵箱中;
??? 3. 用戶點(diǎn)擊郵箱中的激活鏈來接激活帳號(hào);
??? 4. 用戶帳號(hào)激活成功,并給出帳號(hào)激活成功的提示消息。
??? 根據(jù)上面的功能需求,我們?cè)谇懊鎯蓚€(gè)故事的基礎(chǔ)上再添兩筆。
### 故事用例之用戶通過郵件激活帳號(hào) ###
????$ gedit features/user_signup.feature
??? 修改后的文件內(nèi)容如下,
??? 功能: 注冊(cè)成為網(wǎng)站會(huì)員
????? 為了能夠?yàn)g覽網(wǎng)站只對(duì)在線會(huì)員可見的那些內(nèi)容
????? 作為一名訪客
????? 我希望注冊(cè)成為網(wǎng)站會(huì)員
????? 場(chǎng)景: 用戶填寫無效數(shù)據(jù)并注冊(cè)
??????? 當(dāng) 我來到用戶注冊(cè)頁面
??????? 而且 我在輸入框<用戶名>中輸入<invalid username>
??????? 而且 我在輸入框<電子郵箱>中輸入<invalid email>
??????? 而且 我在輸入框<密碼>中輸入<password>
??????? 而且 我在輸入框<確認(rèn)密碼>中輸入<verify password>
??????? 而且 我按下<注冊(cè)>按鈕
??????? 那么 我應(yīng)該看到<注冊(cè)失敗>的提示信息
????????
????? 場(chǎng)景: 用戶填寫正確的數(shù)據(jù)并注冊(cè)
??????? 當(dāng) 我來到用戶注冊(cè)頁面
??????? 而且 我在輸入框<用戶名>中輸入<404>
??????? 而且 我在輸入框<電子郵箱>中輸入<xuliicom@gmail.com>
??????? 而且 我在輸入框<密碼>中輸入<password>
??????? 而且 我在輸入框<確認(rèn)密碼>中輸入<password>
??????? 而且 我按下<注冊(cè)>按鈕
??????? 那么 我應(yīng)該看到<注冊(cè)成功>的提示信息
??????? 而且 應(yīng)該有封激活帳號(hào)的郵件發(fā)送至<xuliicom@gmail.com>
????? 場(chǎng)景: 用戶激活帳號(hào)
??????? 假如 我已經(jīng)使用<404/xuliicom@gmail.com/password>注冊(cè)過
??????? 當(dāng) 我訪問<xuliicom@gmail.com>郵件中激活帳號(hào)的鏈接
??????? 那么 我應(yīng)該看到<帳號(hào)激活成功>的提示信息
??? 我們只是在已有的故事上加了個(gè)別子句。為了能讓故事跑起來,我們還需要針對(duì)故事場(chǎng)景中的情節(jié)編寫相應(yīng)的測(cè)試代碼。
### 編寫用于驅(qū)動(dòng)故事運(yùn)行的測(cè)試代碼 ###
????$ gedit features/step_definitions/user_steps.rb
??? 添加如下代碼,
??? Then /^應(yīng)該有封激活帳號(hào)的郵件發(fā)送至<(.+)>$/ do |email|
????? user = User.find_by_email(email)
????? user.activation_token.should_not be_blank
????? sent = ActionMailer::Base.deliveries.last
????? sent.to.should eql([user.email])
????? sent.subject.should =~ /激活/
????? sent.body.should =~ /#{user.activation_token}/
??? end
??? Given /^我已經(jīng)使用<(.*)\/(.*)\/(.*)>注冊(cè)過$/ do |username, email, password|
????? @valid_attributes = {
??????? :username????????????? => username,?
??????? :email???????????????? => email,?
??????? :password????????????? => password,?
??????? :password_confirmation => password
????? }
????? @user = User.create!(@valid_attributes)
??? end
??? When /^我訪問<(.*)>郵件中激活帳號(hào)的鏈接$/ do |email|
????? user = User.find_by_email(email)
????? visit activate_url(:token => user.activation_token)
??? end
??? 故事用例基本上涵蓋了我們開發(fā)的用意,測(cè)試代碼準(zhǔn)備就緒,還等什么,趕緊跑起來看看吖。
??? 運(yùn)行測(cè)試,
????$ ruby script/cucumber -l zh-CN features/user_signup.feature
????
??? 測(cè)試未能通過,原本應(yīng)該有封激活帳號(hào)的郵件發(fā)送至<xuliicom@gmail.com>,然而卻沒有,因?yàn)槲覀冞€沒有編寫用于發(fā)送激活郵件的代碼。習(xí)慣了玩測(cè)試的話,測(cè)試結(jié)果無疑對(duì)指導(dǎo)你的編碼工作非常有幫助!
??? 接下來,我們就來做這些工作。
### 添加激活碼字段 ###
??? 怎么知道用戶有沒有激活帳號(hào)呢?答案是在 users 表中增加用于標(biāo)識(shí)用戶帳號(hào)是否激活的兩個(gè)字段,一個(gè)用來存放激活碼,另一個(gè)用來記錄帳號(hào)激活時(shí)間。假設(shè)這兩個(gè)字段分別是 activation_token 和 activated_at,如果 users.activation_token 字段有值,那么就說明用戶還沒有激活,如果 users.activation_token 為空且 users.activated_at 有值,那么就說明用戶已經(jīng)激活過了。
??? 下面來添加這組字段,
????$ ruby script/generate migration EmailConfirm
????$ gedit db/migrate/*_email_confirm.rb
??? class EmailConfirm < ActiveRecord::Migration
????? def self.up
??????? add_column :users, :activation_token, :string
??????? add_column :users, :activated_at, :datetime
????? end
????? def self.down
??????? remove_column :users, :activated_at
??????? remove_column :users, :activation_token
????? end
??? end
????$ rake db:migrate
????$ rake db:test:prepare
??? 表結(jié)構(gòu)準(zhǔn)備完畢后,再來生成用戶注冊(cè)時(shí)的激活碼。
### 生成激活碼——activation_token ###
????$ gedit app/models/user.rb
??? before_create :initialize_salt, :encrypt_password, :initialize_activation_token
??? # 生成并返回標(biāo)識(shí)碼
??? def generate_token
????? encrypt(Time.now.to_s.split(//).sort_by {rand}.join)
??? end
??? # 生成激活碼
??? def initialize_activation_token
????? if new_record?
??????? self.activation_token = generate_token
????? end
??? end
??? 數(shù)據(jù)模型搞定后,再從路由下手,需要指定控制器該如何分配響應(yīng)請(qǐng)求。
### 配置激活帳號(hào)的路由——activate_url ###
????$ gedit config/routes.rb
??? 修改后routes.rb文件內(nèi)容如下,
??? ActionController::Routing::Routes.draw do |map|
????? map.with_options :controller => 'users' do |page|
??????? page.signup '/signup', :action => 'new'
??????? page.activate '/activate/:token', :action => 'activate'
????? end
????? map.resources :users
??? end
??? 此時(shí),如果你不清楚接下來要做什么;不妨運(yùn)行測(cè)試,測(cè)試結(jié)果會(huì)告訴你答案。由于筆者知道會(huì)失敗也知曉接下里該做什么,所以就略過此步;因?yàn)镸odel和Route都準(zhǔn)備完畢,是時(shí)候動(dòng)手編寫業(yè)務(wù)流程了。
??? 如果你用Rails發(fā)過郵件,下面的步驟你一定很熟悉。
### 生成郵件 ###
????$ ruby script/generate mailer UserMailer confirm
????$ gedit app/models/user_mailer.rb
??? class UserMailer < ActionMailer::Base
??????
????? def confirm(user, sent_at = Time.now)
??????? subject??? '請(qǐng)激活您的帳號(hào)'
??????? recipients user.email
??????? from?????? 'Admin'
??????? sent_on??? sent_at
????????
??????? body?????? :username => user.username,?
?????????????????? :url? => activate_url(:token => user.activation_token)
????? end
??? end
????$ gedit app/views/user_mailer/confirm.erb
??? 親愛的 <%=@username%>:
??????? 您的帳號(hào)已經(jīng)創(chuàng)建成功,請(qǐng)點(diǎn)擊下面的鏈接激活您的帳號(hào):
??????? <%=link_to @url, @url%>
### 發(fā)送郵件 ###
??? 用戶注冊(cè)成功之后,需要發(fā)送一封確認(rèn)郵件到用戶注冊(cè)時(shí)填寫的電子郵箱中。雖然可以在 User 模型中添加 after_create 的一個(gè)回調(diào)代碼來執(zhí)行,但這樣就給 User 模型類增添了本不應(yīng)該承擔(dān)的責(zé)任;我們只需要 User 模型提供數(shù)據(jù),而不是將發(fā)送郵件的任務(wù)丟給它。這時(shí)候 ActiveRecord 提供的 Observer 就可以派上用場(chǎng)了,使用Observer的好處是它可以將自身連接到模型類中并注冊(cè)為回調(diào),卻無需修改任務(wù)模型類的代碼,我們將其稱之為觀察器(是否聯(lián)想到Ruby設(shè)計(jì)模式中的觀察者模式,呵呵)。下面,我們針對(duì) User 模型創(chuàng)建一個(gè)觀察器:
????$ ruby script/generate observer User
????$ gedit app/models/user_observer.rb
??? class UserObserver < ActiveRecord::Observer
????? def after_create(user)
??????? UserMailer.deliver_confirm(user)
????? end
??? end
??? 然后在 config/environment.rb 注冊(cè)這個(gè) Observer。
????$ gedit config/environment.rb
????config.active_record.observers = :user_observer
??? 再次發(fā)動(dòng)測(cè)試引擎,看看是否working,
????$ ruby script/cucumber -l zh-CN features/user_signup.feature
????
??? 由于在生成郵件那一章節(jié)里,激活鏈接我們用的是 link_url 這種形式,如果你知道 link_url 和 link_path 的區(qū)別,那么根據(jù)上面的測(cè)試結(jié)果,你應(yīng)該了解出錯(cuò)的原因。如果不了解,筆者在這里補(bǔ)充下,link_url 會(huì)在鏈接中加上協(xié)議名、主機(jī)名和端口號(hào)這些;而 link_path 則不用,它會(huì)直接用根目錄“/”代替之;也就是說, link_url 會(huì)在鏈接中加上網(wǎng)址;又或者說,link_url 采用絕對(duì)路徑,而 link_path 采用相對(duì)路徑。
??? 考慮到現(xiàn)實(shí)中的用戶注冊(cè),系統(tǒng)會(huì)發(fā)送一封包含網(wǎng)址的郵件到注冊(cè)用戶的郵箱中,我們之前的郵件模板里不得不采用 link_url 這種形式。結(jié)合測(cè)試結(jié)果來看,也許此時(shí)您已經(jīng)意識(shí)到,我們是不是忘了配置主機(jī)名呢?
??? 恭喜您!您確實(shí)猜對(duì)了。
### 配置郵件中激活鏈接的絕對(duì)路徑 ###
????$ gedit app/models/user_mailer.rb
????default_url_options[:host] = HOST
??? 在 config/environments/test.rb 和 config/environments/development.rb 這兩個(gè)配置文件中定義 HOST 常量,為了開發(fā)和測(cè)試需要,這里設(shè)置成localhost就可以了。
????HOST = 'localhost:3000'
??? 不過在 config/environments/production.rb 中,HOST 常量的值就必須是真實(shí)的主機(jī)名了。
??? 另一種方法無需修改app/models/user_mailer.rb和定義HOST常量,直接在各environment/各文件或environment.rb中配置就行了,如下代碼
????$ gedit config/environment.rb
????config.action_mailer.default_url_options = { :host => 'localhost:3000' }
??? 這樣做的好處是只需修改一處。
??? 好了,補(bǔ)上這個(gè)配置,再運(yùn)行測(cè)試,看看有什么不同。
????$ ruby script/cucumber -l zh-CN features/user_signup.feature
????
### 激活帳號(hào) ###
??? 看來我們的郵件能夠成功發(fā)送了,不過好像訪問郵件中的確認(rèn)鏈接時(shí)出了點(diǎn)問題,根據(jù)調(diào)試信息“ActionController::UnknownAction”顯示,應(yīng)該是沒有找到激活帳號(hào)的具體行為(action)。在前面的開發(fā)中,我們真的就還沒有編寫響應(yīng)用戶激活帳號(hào)的相關(guān)代碼,想必此時(shí)我們都清楚該做哪些工作了。
??? 我們需要給 UserController 類添加一個(gè) Action 來響應(yīng)用戶激活帳號(hào)的請(qǐng)求。
????$ gedit app/controllers/users_controller.rb
??? 之前我們?cè)赾onfig/routes.rb文件中定義了activate_path,且該activate_path 的 :action 參數(shù)指向 activate 方法;于是乎,activate 就是我們需要在 UserController 類中添加的 action。activate方法的代碼如下:
??? def activate
????? if @user = User.find_by_activation_token(params[:token])
??????? if !@user.activated?
????????? @user.email_confirm!
????????? flash.now[:notice] = '恭喜您,帳號(hào)激活成功!'
??????? end
????? end
??? end
??? 仔細(xì)觀察 UserController#activate,我們還需要在 User 模型中編寫 activated? 和 email_confirm! 這兩個(gè)實(shí)例方法,前者用來確認(rèn)用戶的帳號(hào)是否已經(jīng)激活過,后者則用來激活用戶的帳號(hào)。
????$ gedit app/models/user.rb
??? 在 protected 之前添加如下兩個(gè)方法:
??? # 檢查是否已經(jīng)激活
??? def activated?
????? # 當(dāng) activation_token 為 nil 時(shí)表示用戶帳號(hào)已經(jīng)激活
????? activation_token.nil?
??? end
??????
??? # 激活帳號(hào)
??? def email_confirm!
????? update_attributes(:activation_token => nil, :activated_at => Time.now)
??? end
??? 運(yùn)行測(cè)試看看,
????$ ruby script/cucumber -l zh-CN features/user_signup.feature
????
??? 看來是沒有找到模板文件,在此補(bǔ)上用戶成功激活帳號(hào)的頁面。
????$ gedit app/views/users/activate.html.erb
??? 保存即可。運(yùn)行測(cè)試:
????$ ruby script/cucumber -l zh-CN features/user_signup.feature
??? OK,測(cè)試通過!如圖,
????
### 親臨現(xiàn)場(chǎng) ###
??? 最后開發(fā)人員自己別忘了手工測(cè)試,以確保萬無一失。
??? 先清除數(shù)據(jù)庫中的記錄,
????$ ruby script/console
????>> User.delete_all
??? 假設(shè)我們以404為用戶名成功注冊(cè)后,我們來看看數(shù)據(jù)庫中404的activation_token字段是否有值。
????>> User.find_by_username('404', :select => "username, activation_token, activated_at")
????
??? 可以看到,activation_token 的值是一串加密后的字符,activated_at值為空,這說明程序已經(jīng)給注冊(cè)用戶生成了激活碼,而且此時(shí)用戶還沒有激活帳號(hào)。
??? 當(dāng)我們注冊(cè)成功后,打開郵箱卻并沒有看到激活帳號(hào)的郵件,這是怎么回事呢?
??? 因?yàn)闇y(cè)試程序跑到是test環(huán)境,而我們手工測(cè)試的時(shí)候,程序是運(yùn)行在development環(huán)境下的,我們沒有針對(duì)development環(huán)境配置郵件服務(wù)器。下面我們采用SMTP的發(fā)信方式,這里的SMTP SERVER用的是GMAIL,而且是SSL驗(yàn)證登錄方式;三次握手,發(fā)信速度沒sendmail那么快,呵呵!
????$ gedit config/environment.rb
??? config.action_mailer.delivery_method = :smtp
??? config.action_mailer.default_charset = 'utf-8'
??? config.action_mailer.smtp_settings = {
????? :address????????????? => 'smtp.gmail.com',
????? :port???????????????? => 25,
????? :domain?????????????? => 'YOUR_DOMAIN',
????? :user_name??????????? => 'YOUR_GMAL_USERNAME',
????? :password???????????? => 'YOUR_GMAIL_PASSWORD',
????? :authentication?????? => 'login',
????? :enable_starttls_auto => true
??? }
??? 該配置中大寫部分自行替換即可。
??? 清空users表,我們重新注冊(cè)404這個(gè)用戶。
????$ ruby script/console
????>> User.delete_all
??? 然后去郵箱看看,
????
??? 這回我們打開郵箱看到了激活帳號(hào)的郵件信息,不過郵件內(nèi)容中的鏈接標(biāo)簽沒有生效,我們期望發(fā)送到用戶郵箱的是HTML格式的郵件。ActionMailer可以讓我們發(fā)送多種格式的郵件,只需要按相應(yīng)的內(nèi)容類型修改郵件模板的文件名格式即可。基本上,郵件模板的文件名的格式像這樣:name[.content.type].renderer;content.type 可選,缺省情況下為文本格式,你也可以手工指定為 text.plain,要發(fā)送HTML格式的郵件就需要指定為 text.html;文件后綴 renderer 一般情況下都是 erb(如果你用了HAML插件,模板后綴名應(yīng)該是haml)。下面我們將之前文本格式的郵件模板修改為網(wǎng)頁形式的:
????$ mv app/views/user_mailer/confirm.erb app/views/user_mailer/confirm.text.html.erb
??? 再次清空users表,重新注冊(cè)404這個(gè)用戶,然后前往郵箱看看我們收到的郵件是否是網(wǎng)頁格式的。
????
??? 我們看到激活帳號(hào)的超鏈接生效了(沒有將超鏈接標(biāo)簽明文顯示),這說明系統(tǒng)發(fā)送出去的確實(shí)是HTML郵件。
??? 接下來我們點(diǎn)擊郵件中的鏈接來到了激活帳號(hào)的頁面,我們看到帳號(hào)激活成功的提示信息。
????
??? 如果激活成功,數(shù)據(jù)庫中的activation_token字段應(yīng)該是空值,且activated_at字段的值應(yīng)該為一時(shí)間戳;在之前的程序中,我們確實(shí)是按此邏輯編碼的。雖然測(cè)試成功,而且我們也非常順利地親歷了一遍注冊(cè)流程,那么是否就說明我們的應(yīng)用程序沒有程序上的漏洞了嗎?我們真的激活帳號(hào)了嗎?我們不妨看看數(shù)據(jù)庫這只黑匣子,此時(shí)應(yīng)該是讓數(shù)據(jù)說話的時(shí)候了。
????$ ruby script/console
????>> User.find_by_username('404', :select => "username, activation_token, activated_at")
????
??? 哎呀!記錄居然沒被更新,看來我們被表面現(xiàn)象給忽悠了。我想你此時(shí)也和我一樣迷惑,為什么數(shù)據(jù)記錄沒有被更新呢?這中間到底發(fā)生了什么?這讓我不由自主地想象起來,也許Rails的ORM真的修改了User實(shí)例對(duì)象的activation_token和activated_at屬性的值,只不過還沒有成功地寫入到數(shù)據(jù)庫里邊而已。果真如此嗎?如何證明這一說法成立呢?我們來看看User模型類的 email_confirm! 方法,下面是email_confirm! 方法的源碼:
??? # 激活帳號(hào)
??? def email_confirm!
????? update_attributes(:activation_token => nil, :activated_at => Time.now)
??? end
??? 我們知道,update_attributes 方法還有一個(gè)和自己長(zhǎng)得差不多一樣的方法,即 update_attributes!
;后者比前者僅僅多一個(gè)感嘆號(hào)而已,兩者都是更新當(dāng)前模型對(duì)象所指向的數(shù)據(jù)記錄,只不過前者更新失敗會(huì)返回false,后者更新失敗則會(huì)拋出異常信息并停止程序運(yùn)行,我們不妨用 update_attributes! 替換 email_confirm!方法中的update_attributes,如果問題真的出現(xiàn)在這里,至少我們也可以看見拋出的錯(cuò)誤信息,這些錯(cuò)誤調(diào)試信息對(duì)開發(fā)人員來說是那么的重要。
????$ gedit app/models/user.rb
??? 修改 email_confirm! 方法如下:
??? def email_confirm!
????? update_attributes!(:activation_token => nil, :activated_at => Time.now)
??? end
??? 保存,然后重新訪問或刷新激活帳號(hào)的頁面,我們看到系統(tǒng)捕獲到了非常實(shí)用的情報(bào),如圖:
????
??? 看來問題還真的出在User模型類的email_confirm!方法這里,當(dāng)程序嘗試更新 activation_token 和 activated_at 這兩個(gè)字段時(shí),系統(tǒng)告訴我們密碼不能為空并就此打住,程序拋出錯(cuò)誤并停止執(zhí)行,后面當(dāng)然不會(huì)更新數(shù)據(jù)庫里邊的記錄了。找到出錯(cuò)的原因后,我們馬上就明白 update_attributes(或update_attributes!) 會(huì)更新當(dāng)前對(duì)象所指向的記錄的所有字段,并在更新之前執(zhí)行數(shù)據(jù)校驗(yàn),如果校驗(yàn)失敗就會(huì)打斷程序的運(yùn)行。想到此,針對(duì)問題的解決方案也初現(xiàn)輪廓,只要程序更新指定的字段,并在更新這些指定字段的時(shí)候不去校驗(yàn)其他字段的數(shù)據(jù)有效性就行了。OK,我們有非常適合用于email_confirm!的替代寫法,不妨修改email_confirm!方法如下:
????$ gedit app/models/user.rb
??? # 激活帳號(hào)
??? def email_confirm!
????? self.activation_token = nil
????? self.activated_at = Time.now
????? save(false)
??? end
??? 上述代碼中的 save 方法會(huì)更新這些字段的值,第一個(gè)參數(shù)的值指明為false后將不會(huì)執(zhí)行數(shù)據(jù)校驗(yàn),看來這一切和我們的想法非常吻合,不妨保存user.rb再刷新幾次瀏覽器看看。第一次刷新和我們初次訪問激活鏈接看到的效果一樣,都是提示帳號(hào)激活成功,后面幾次就看不到激活成功的消息了,因?yàn)閹ぬ?hào)只需要激活成功一次就足夠了,效果確實(shí)很理想,我們?nèi)ピL問下數(shù)據(jù)庫讓它給我們做個(gè)見證。
????$ ruby script/console
????>> User.find_by_username('404', :select => "username, activation_token, activated_at")
????
??? 哈哈,數(shù)據(jù)記錄已經(jīng)更新了,這意味著程序已經(jīng)可以按照我們之前的意愿運(yùn)行了。用戶提交注冊(cè)資料后會(huì)收到一封關(guān)于激活帳號(hào)的郵件,然后點(diǎn)擊其中的鏈接可以成功激活他的帳號(hào)。
??? 至此,我們?cè)?email_activation 分支上的開發(fā)工作已經(jīng)順利完成,可以將工作成果歸并到主干中去了。
### 提交工作成果到GIT倉庫 ###
??? $ git add .
??? $ git commit -m?"People can activation their accounts by the confirm emails."
??? $ git checkout master
??? $ git merge email_activation
??? $ git branch -d email_activation
??? $ git tag v2
??? (注意,真正的開發(fā)中可不是到功能開發(fā)完畢了才commit,而是邊開發(fā)邊add和commit。為了方便演示編碼過程,文章中沒有一一列舉。)
### 小結(jié) ###
??? 在這篇教程中,我們的開發(fā)工作遇到了不小的挫折,尤其是在人工測(cè)試那里,經(jīng)過我們自己動(dòng)手測(cè)試后,才知曉我們的程序漏洞百出。之所以這樣,是由于筆者有意而為之,其實(shí)筆者的用意非常簡(jiǎn)單,就是想告訴開發(fā)者親臨現(xiàn)場(chǎng)做人工測(cè)試的重要性。也許確實(shí)讓您受挫了,覺得好像是為了測(cè)試而測(cè)試似的;大可不必有如此想法,如果您是位Rails熟手,想必也不會(huì)犯那些低級(jí)錯(cuò)誤,比如update_attributes和save(false)這種區(qū)別及其應(yīng)用場(chǎng)合,也會(huì)知曉 test/development/production 這幾種環(huán)境的區(qū)別;那也就避免了些不必要的麻煩。經(jīng)驗(yàn)是慢慢積累的,過程可以幫我們汲取經(jīng)驗(yàn)。等您自己應(yīng)用熟練了,我相信您能體會(huì)到測(cè)試帶來的好處。
### 下節(jié)預(yù)告 ###
??? 接下來我們依然是借助cucumber+rspec來驅(qū)動(dòng)用戶登錄功能的開發(fā),看測(cè)試跟session和cookie打交道。如果有興趣,期待您能夠下次光臨!如果有好的建議和經(jīng)驗(yàn)非常希望能夠與您交流,您可以在下面發(fā)表留言或者和我email聯(lián)系,我的郵箱是 xuliicom@gmail.com。
標(biāo)簽:?Cucumber,?Rails,?Rspec,?TDD
Posted by 404轉(zhuǎn)載于:https://www.cnblogs.com/ToDoToTry/archive/2011/09/10/2173390.html
總結(jié)
以上是生活随笔為你收集整理的使用Cucumber+Rspec玩转BDD(2)——邮件激活的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 域名解析文件hosts文件是什么?如何修
 - 下一篇: 大数据决策支持的优势