原文:Behavior Driven Design (並不是公開版權文檔)
你可能已經注意到,驗證模對象改變了你在單元測試中執行的那類測試的本質。傳統的單元測試中,經過斷言(Assertion)來進行大多數的驗證。這是一種對應用程序狀態的測試。模對象的測試,是測試測試中調用過程十分符合預期。它測試的是應用程序的行爲。至少潛在能力上,行爲測試更容易將指望行爲與實際的實現方法分開。
行爲驅動設計(BDD)的口號是試圖將測試設計從實現空間移到問題空間。部分地使用設計BDD工具集,測試能夠經過指定更接近天然語言的關鍵詞來進行。BDD工具充分利用模對象,既做爲說明問題的方法,也做爲區分單獨的個別單元測試的方法。使用傳統的TDD測試,低層方法的修改會致使不少測試失敗。BDD認爲一個代碼的改變能產生許多測試失敗代表這個測試不是真正意義上的單元測試,而是集成測試,儘管是很是小的範圍內。TDD方式的測試只有在實際對象不能獲得或者很差用的狀況下才會使用模對象,BDD方式則更加積極地使用模對象來將測試中的函數與系統的其它部分分離。
這個章節將概要地介紹一下RSpec(Ruby中最流行的BDD測試包)。RSpec與Rails集成得很是好,包括單獨地測試Controller, View和Helper函數的能力。
安裝RSpec
RSpec便可以做爲Ruby gem也能夠做爲Rails plugin。在Rails裏面使用時,可使用下面的命令同時安裝RSpec plugin和RSpec Rails plugin。
Ruby代碼
ruby script/plugin install -x svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec
ruby script/plugin install -x svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails
ruby script/plugin install -x svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec
ruby script/plugin install -x svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails
一般,若是你不想將RSpec做爲擴展組件安裝到你的版本控制服務器中的話,你能夠將-x選項去掉。
Windows用戶還須要爲RSpec安裝一個叫win32console的gem來更好的運行RSpec。
在安裝好RSpec以後,你須要運行下面命令來生成RSpec的路徑與文件。
Ruby代碼
ruby script/generate rspec
ruby script/generate rspec
這個命令的最主要的目的是在Rails的根目錄下建立一個spec子目錄,裏面有一個相似標準test_helper.rb文件的spec_helper.rb。它還建立了一些腳本,另外一些文件你目前還用不着。然而,這個命令不會建立符合Rails命名規則的子目錄。你須要手工來作。
RSpec使用Rails現有的命名規則。下面的表格能夠看出它經過簡單的名字來命名的。
Test Type Example Name
Controller test spec/controllers/reciper_controller_spec.rb
Helper test spec/helpers/reciper_helper_spec.rb
Model test spec/models/reciper_spec.rb
View test spec/views/recipe/new_spec.rb
在這個章節中,因爲咱們已經生成了Model, View, Controller和Helper文件,因此咱們將要手工建立這些規格文件。
若是你是用RSpec新建項目的話,可使用: rspec_controller, rspec_model和rspec_scaffold這3個RSpec自定義的生成器。它們和原來的生成器形式相同,使用同樣的參數。惟一的區別是在spec目錄下建立了RSpec的原始測試文件,而不是test目錄下的Test::Unit文件。
你可使用rake spec命令來運行全部的RSpec規格。若是你只想運行其中的一套,你能夠用更明確的命令,例如:rake spec:models。一共有:controllers, helpers, models, plugings和views幾個子任務。若是你安裝了rcov,全部命令後面均可以加上「:rcov」來生成一個覆蓋報告(用gem install rcov來安裝rcov)。可是,就算有爲view模板寫RSpec測試,rcov也不會爲ERB模板生成覆蓋報告。在Rails 2.0中,默認的rake任務會同時運行test目錄下的測試和spec目錄下的RSpec規格。
寫RSpec規格
一個RSpec規格文件包含一個和多個行爲,每個包含有一個或多個例子。注意,命名方式已經代表了RSpec與Test::Unit之間的區別:"行爲"和"例子"討論的是測試中功能和指望的概念,而"測試"和"斷言"則關注實現。一個RSpec行爲大約與一個Test::Unit類等價,雖然你常常能夠看到在一個規格文件中有多個行爲,而不多看到一個Test::Unit類文件中有多個類。
描述一個行爲的方法是:describe,而描述一個例子的方法是:it。一個規格文件的框架大概是這個樣子:
Ruby代碼
describe Foo do
it 「should not crash when I call it」 do
# do something testable here
end
end
describe Foo do
it 「should not crash when I call it」 do
# do something testable here
end
end
能夠注意到這個例子是用天然語言來描述的而不是用函數名。
在一個行爲中,你能夠經過"befort"和"after"函數來指定初始環境和清除。這些函數有兩個用法。默認的是「:each」,表示每個單獨的例子運行以前或以後將要運行的代碼塊,就像在Test::Unit裏面的setup和teardown函數同樣。另外一個用法是":all",表示在全部例子運行先後全部例子完成以後運行的代碼塊。你能夠用一樣的方法指定多個before和after塊,全部的塊都會在合適的時間執行。
在一個行爲中,任何用Ruby普通的def關鍵字聲明的函數能夠被全部行爲中的例子使用,這樣你就能夠寫一些自定義的驗證函數和通用函數。
而且,雖然例子一般有一個代碼塊,可是你也能夠臨時僅僅爲例子指定一個字符串而不提供代碼塊,例如:
Ruby代碼
it 「should do something that hasn’t been implemented yet」
it 「should do something that hasn’t been implemented yet」
RSpec將把這個測試標示爲待完成,而且報表會將待完成的數量從經過數量或失敗數量中分離出來。
你也能夠再進一步,有一段代碼,你知道會出錯的測試,可是仍是但願測試經過,一般的樣子是這樣的:
Ruby代碼
it 「should fix this silly bug」 do
pending(「this is Bob’s problem」) do
#specify the failing test here
end
end
it 「should fix this silly bug」 do
pending(「this is Bob’s problem」) do
#specify the failing test here
end
end
在這個用例裏,RSpec將運行pending塊中的代碼,若是失敗,將報告測試待完成,若是成功,將獲得一個預期的失敗,告訴你這個測試再也不失敗,你不須要用pending了。
寫Model測試
接下來的幾個部分,你將看到一些基於已經寫的Test::Unit測試上RSpec規範的一些例子。測試目標由於已經寫過,這裏再也不徹底從新說明,咱們着重於看看RSpec的測試是怎樣工做的,並看看它們與Test::Unit之間的區別。咱們先看看Model。
爲了能讓這些測試運行,須要將test/fixtures裏的YAML文件複製到spec。下面的代碼是從「spec/models/recipe_spec.rb」複製過來的:
Ruby代碼
require File.dirname(__FILE__) + ‘/../spec_helper’
describe Recipe, 「basic test suite」 do
fixtures :recipes
fixtures :ingredients
it 「should have ingredients in order」 do
subject = Recipe.find(1)
subject.ingredients.collect { |i| i.order_of }.should == [1, 2, 3]
end
end
require File.dirname(__FILE__) + ‘/../spec_helper’
describe Recipe, 「basic test suite」 do
fixtures :recipes
fixtures :ingredients
it 「should have ingredients in order」 do
subject = Recipe.find(1)
subject.ingredients.collect { |i| i.order_of }.should == [1, 2, 3]
end
end
這個幾乎是從第一章的單元測試直接翻譯過來的。describe塊創建了行爲(尚未before和after代碼)。it快創建了一個指定的指望:這個用例,咱們指望ingredient成員老是排序的。
should函數(和他的兄弟should_not)是RSpec中測試狀態的關鍵字。它後面緊接"=="。這個方法用來執行一個斷言,右邊的值是但願的值,左邊的值(接在should以後)是實際的值。
你能夠在should以後接任何東西。特別的,任何be_<something>形式的語句都會自動地被RSpec翻譯成<something>?。由於nul?在全部對象中都被定義,你老是能夠用should be_nil和should_not be_nil來測試。在一個Rails項目中,你老是能夠測試should be_blank;數組能夠測試should be_empty;等等。若是你想更方便閱讀,你能夠加上be_a或者be_an前綴。RSpec也很是聰明,能夠在have/has之間作調整,這樣一個hash的should have_key將測試has_key?術語。記住,咱們這裏說的都是在名字中用了問號的方法,即便你在RSpec中不使用問號。
第一個測試確實與原來的單元測試相似,下一個測試顯示出更多RSpec與Test::Unit之間的區別。在第一章中,咱們寫了一堆測試來驗證一個Ingredient對象中解析一個像"2 cups of carrots, chopped"這樣字符串的代碼。還有一個測試來驗證一個recipe能接受不少這樣字符串,而且將它們轉換成一個ingredient的列表。用RSpec寫的的recipe的測試以下:
Ruby代碼
it 「should split ingredient strings into separate lines」 do
Ingredient.should_receive(:parse).exactly(3).times.and_return do |s, rec, ord|
Ingredient.new(:recipe_id = > rec.id,
rder_of = > ord, :amount = > 2,
:ingredient = > ord)
end
subject = Recipe.find(2)
subject.ingredient_string =
「2 cups carrots, diced\n\n1/2 tablespoon salt\n\n1 1/3 cups stock」
subject.ingredients.count.should == 3
subject.ingredients.collect { |i| i.order_of }.should == [1, 2, 3]
subject.ingredients.collect { |i| i.ingredient }.should == %w[1 2 3]
end
it 「should split ingredient strings into separate lines」 do
Ingredient.should_receive(:parse).exactly(3).times.and_return do |s, rec, ord|
Ingredient.new(:recipe_id = > rec.id,
rder_of = > ord, :amount = > 2,
:ingredient = > ord)
end
subject = Recipe.find(2)
subject.ingredient_string =
「2 cups carrots, diced\n\n1/2 tablespoon salt\n\n1 1/3 cups stock」
subject.ingredients.count.should == 3
subject.ingredients.collect { |i| i.order_of }.should == [1, 2, 3]
subject.ingredients.collect { |i| i.ingredient }.should == %w[1 2 3]
end
測試中有兩個部分。爲recipe對象提供了一個包含3個ingredient和兩個空行的字符串。咱們假定recipe會忽視空行而解析其餘行。
這Test::Unit的測試中,調用了ingredient的parser函數,而且指定了用來獲得指望解析結果的recipe中的對象。在RSpec的測試中,你不須要調用ingredient的parser函數,由於你不須要測試它。取而代之的是第一行中,你你建立了一個Ingredient類的臨時模對象,返回一個虛擬的ingredient對象,而且斷言它將正好被調用3次。這個虛擬的ingredient對象並不比較解析器實際的輸出(它們僅僅爲了避免產生nil異常而建立了最小限度的數據),可是你徹底沒必要關心:解析器是否正確會在後面的解析器的測試中進行。這個測試僅僅是測試recipe是否是可以接受有空行的數據,你沒必要關心解析器是否是正確。這裏的關鍵是關注的測試中的實際函數而且爲這個函數創建一圈圍牆來讓它完成測試。
RSpec使用它本身的模對象規範,和FlexMock相似。(若是你已經使用過FlexMock或者其餘Ruby的模對象工具,相似地配置RSpec很簡單)。模規範should_receive與FlexMock的模認證規範很相似。它能夠用once, twice, times(n), at_least和at_most參數來指定全部的值。除了and_return,你還能夠用帶異常參數的and_raise來驗證在錯誤環境下測試獲得的異常。用and_yield來指定一個代碼塊參數,爲不是一個值。
在這個測試中使用的模定義了一個動態部分。由於你爲and_return設置了一段代碼參數,因此你能夠爲經過設置參數,獲得指定recipe id和order值的ingredient對象。雖然一般使用靜態無互聯的模對象比較好,在這個用例中有一個須要被驗證的關聯關係。ingredient的order不是在parser自身設置的,而是經過recipe對象參數傳進來的。若是ingredient的order是在模對象中靜態設置的,就沒有辦法驗證order是否是真的被設置了。所以,像上面的代碼,這個測試容許經過設置參數來動態設置order,而且爲每一個ingredient設置指定的order,這個就能夠徹底驗證recipe的order。
上面的測試是說明RSpec怎樣讓你能夠將行爲測試和狀態測試混合起來的好例子。RSpec也能夠將函數名處理成易於閱讀的形式。當我開始爲ingredient解析器寫集成測試的時候,我決定徹底利用這一優點。我在「spec/model/ingredient_spec.rb」中編寫了如下代碼:
Ruby代碼
require File.dirname(__FILE__) + ‘/../spec_helper’
class String
def parsing_to?(hash)
expected = Ingredient.new(hash)
actual = Ingredient.parse(self, Recipe.find(1), 1)
actual == expected
end
end
describe Ingredient, 「basic parsing suite」 do
fixtures :ingredients, :recipes
it 「should parse a basic string」 do
「2 cups carrots, diced」.should be_parsing_to(:recipe_id = > 1,
rder_of = > 1, :amount = > 2, :unit = > 「cups」,
:ingredient = > 「carrots」, :instruction = > 「diced」)
end
end
require File.dirname(__FILE__) + ‘/../spec_helper’
class String
def parsing_to?(hash)
expected = Ingredient.new(hash)
actual = Ingredient.parse(self, Recipe.find(1), 1)
actual == expected
end
end
describe Ingredient, 「basic parsing suite」 do
fixtures :ingredients, :recipes
it 「should parse a basic string」 do
「2 cups carrots, diced」.should be_parsing_to(:recipe_id = > 1,
rder_of = > 1, :amount = > 2, :unit = > 「cups」, :ingredient = > 「carrots」, :instruction = > 「diced」) end end 其實這個例子中惟一特別的地方就是我爲String類增長了一個函數。這也正是那些正統工程師在學習到Ruby能夠爲已有類隨意增長函數時感到緊張的地方。 無論怎樣,由於這個函數只在測試中使用,我想就編程穩定性的觀點來看仍是沒問題的。並且通過RSpec的神奇名字處理,「"2 cups carrots, diced".should be_parsing_to」這樣的描述確實是更加豐富而易懂。實際上,我以爲是否是有必要爲程序自己添加一個String#parse_to_ingredient函數,不過先就這樣吧。 寫控制器的規格書 RSpec在將controller, view和helper分開各自測試的處理上與標準的Rails測試工具比起來有不少優點。控制器的測試放在"spec/controllers"目錄下。這裏爲RecipesController規格書開了個頭,我寫了驗證index函數的運行狀況的測試,而後經過HTML的GET方法調用new函數。第一個部分經過before(:each)方法來建立一個recipe的模對象。爲了單獨測試Controller而不使用Model和數據庫,你須要使用RSpec樁來阻止ActiveRecord類的new或者find函數的調用,這樣能夠不返回實際的ActiveRecord對象,而是返回模對象。最後,你能夠將全部代碼放在「spec/controllers/recipes_controller_spec.rb」裏面: Ruby代碼 require File.dirname(__FILE__) + ‘/../spec_helper’ describe RecipesController do before(:each) do @recipe = mock(「person」) @recipe.stub!(:new_record?).and_return(false) Recipe.stub!(:new).and_return(@recipe) Recipe.stub!(:find).and_return(@recipe) end ... end require File.dirname(__FILE__) + ‘/../spec_helper’ describe RecipesController do before(:each) do @recipe = mock(「person」) @recipe.stub!(:new_record?).and_return(false) Recipe.stub!(:new).and_return(@recipe) Recipe.stub!(:find).and_return(@recipe) end ... end RSpec的樁的用法與FlexMock方法有些小區別。代碼設置了Recipe#new和Recipe#find的樁,而且前面兩句代碼讓它們返回做爲樁的recipe實例。 第一個規格說明了當用戶執行index函數請求時所但願獲得的結果: Ruby代碼 it 「should get an index when requested」 do get 「index」 response.should be_success assigns[:recipes].should_not be_nil end it 「should get an index when requested」 do get 「index」 response.should be_success assigns[:recipes].should_not be_nil end 這個規格里面顯示了一些RSpec用來測試控制器的結果的特殊函數。若是響應返回狀態碼200的話,should be_success將返回true。相相似的response.should be_redirect將測試重定向的狀態。值得說一下,即便你沒有爲Controller寫相關的View,測試should be_success也不會失敗。 測試Controller是否正常渲染了View用帶有但願被調用的模板路徑做爲參數的should render_template來測試,若是Controller只是返回純文本,能夠用should have_text來驗證它的文本內容。若是你要驗證重定向,你能夠用should redirect_to來驗證,它能夠指定一個完整的URL或者一個URL的本地路徑,或者用url_for加上一組Hash選項。 最後一行代碼用assigns的Hash,它相似於標準功能測試裏面的assigns函數,它表示Controller建立的變量實例。你也能夠訪問flash和session等Hash,這樣你能夠驗證Controller中的變量。 第二個Controller的測試像這樣檢查new函數: Ruby代碼 it 「should respond to GET new with a captcha」 do @token = mock_model(Token) captcha = mock(MathCaptcha) MathCaptcha.should_receive(:create).with(3).and_return(captcha) get 「new」 assigns[:captcha].should == captcha end it 「should respond to GET new with a captcha」 do @token = mock_model(Token) captcha = mock(MathCaptcha) MathCaptcha.should_receive(:create).with(3).and_return(captcha) get 「new」 assigns[:captcha].should == captcha end 若是你與傳統的單元測試相比,原來的單元測試中用了大量的assert_select來驗證View中的結果,而在RSpec中將在View的測試中來驗證這些,這裏你只須要測試Controller建立的變量或者數據庫操做。 下面對HTTP的PUT的更新操做的測試顯示了RSpec規格與單元測試之間的區別。 Ruby代碼 it 「should respond to a PUT with an update」 do @recipe.should_receive(:update_attributes).with( {「title」 = > 「Grandma’s Chicken Soup」}).and_return(@recipe) put 「update」, :id = > 1, :recipe = > {:title = > 「Grandma’s Chicken Soup」} response.should redirect_to(「http://test.host/recipes/#{@recipe.id}」) end it 「should respond to a PUT with an update」 do @recipe.should_receive(:update_attributes).with( {「title」 = > 「Grandma’s Chicken Soup」}).and_return(@recipe) put 「update」, :id = > 1, :recipe = > {:title = > 「Grandma’s Chicken Soup」} response.should redirect_to(「http://test.host/recipes/#{@recipe.id}」) end 這個例子很簡單,第一行創建了惟一一個recipe模對象,它表示咱們但願獲得一個update_attibutes調用。第二行執行了一個更新調用,第三行驗證了重定向到一個指定的URL上。一樣,什麼不用測試是很重要的:咱們在這裏不須要測試Recipe在調用update_attibutes以後是否是真的更改了對應的屬性,這裏只測試Controller相關的行爲。 說明View的行爲 要是說關於RSpec我還有什麼須要講的,那就是用一個RSpec例子來講明怎樣經過努力地封裝函數來測試系統的其餘部分。因此你也不用感到驚奇,咱們能夠將View從Controller和數據庫中分離出來測試。下面的例子我將給你說明一下new.html.erb是怎樣渲染面板的。實際上View的大多數工做是在_from子模板中完成的。因此規格應該針對子模板來寫。可是,實際上我假設我在寫測試的時候尚未想好用子模板來處理,因此我寫的規格書是針對整個視圖的。 這個面板上面有一個邏輯點,若是指定了MathCaptcha對象,那麼將顯示這個對象,不然不顯示。另外,須要建立一個Recipe模對象和一個User模對象來進行測試。 下面的代碼在spec/views/recipes/new_spec.rb文件中,顯示了怎樣建立模對象: Ruby代碼 require File.dirname(__FILE__) + ‘/../../spec_helper’ describe ‘recipe/new’ do before(:each) do @recipe = mock_model(Recipe) @recipe.should_receive(:title).and_return(「Grandma’s Soup」) @recipe.should_receive(:servings).and_return(「2」) @recipe.should_receive(:ingredient_string).and_return(「carrots」) @recipe.should_receive(:description).and_return(「description」) @recipe.should_receive(:directions).and_return(「directions」) @recipe.should_receive(:tag_list).and_return(「yummy」) @user = mock_model(User) assigns[:recipe] = @recipe assigns[:user] = @user end ... end require File.dirname(__FILE__) + ‘/../../spec_helper’ describe ‘recipe/new’ do before(:each) do @recipe = mock_model(Recipe) @recipe.should_receive(:title).and_return(「Grandma’s Soup」) @recipe.should_receive(:servings).and_return(「2」) @recipe.should_receive(:ingredient_string).and_return(「carrots」) @recipe.should_receive(:description).and_return(「description」) @recipe.should_receive(:directions).and_return(「directions」) @recipe.should_receive(:tag_list).and_return(「yummy」) @user = mock_model(User) assigns[:recipe] = @recipe assigns[:user] = @user end ... end 如今你對這個模式已經很熟悉了。首先,你建立了一個模實例,而且爲它指定了參數。而後將模對象直接加入到assigns 的Hash中。與Controller測試不一樣,這裏不須要模擬ActiveRecord類。一般View也不像Controller那樣建立對象,因此在View中顯示對象就足夠了。View的測試中也像Controller測試同樣用到了assigns, flash和session。 下面的例子演示了沒有CAPTCHA對象的行爲: Ruby代碼 it 「should display an entire form」 do render 「/recipes/new」 response.should have_tag(「form」) do with_tag 「input[name *= title]」 with_tag 「input[name *= servings]」 with_tag 「textarea[name *= ingredient_string]」 with_tag 「textarea[name *= description]」 with_tag 「textarea[name *= directions]」 with_tag 「input[name *= tag_list]」 end end it 「should display an entire form」 do render 「/recipes/new」 response.should have_tag(「form」) do with_tag 「input[name *= title]」 with_tag 「input[name *= servings]」 with_tag 「textarea[name *= ingredient_string]」 with_tag 「textarea[name *= description]」 with_tag 「textarea[name *= directions]」 with_tag 「input[name *= tag_list]」 end end 想你從代碼中看到的那樣,render函數爲測試模擬執行了指定View。should have_tag和with_tag只是你的老朋友assert_select的同義詞。用一樣的語法來驗證輸出裏的各類各樣HTML元素是否存在。 此外,有一個模板對象,你能夠用來模擬或者樁調用Helper函數。由於你能夠單獨地測試Helper函數。這是一個好主意,使用的是一般的RSpec模框架的語法,看上去像這樣: Ruby代碼 template.stub!(:helper_method).and_return(「flintstone」) template.should_receive(:helper_method).once.and_return(「rubble」) template.stub!(:helper_method).and_return(「flintstone」) template.should_receive(:helper_method).once.and_return(「rubble」) 這個對測試登陸/登出特別有用,由於在咱們的應用程序裏這部本代碼在Helper函數裏。 固然,你也能夠模擬別的測試中的其餘對象。你能夠像這樣經過建立一個模對象來驗證當存在CAPTCHA對象時View的行爲: Ruby代碼 it 「should display captcha」 do @token = mock_model(Token) @token.should_receive(:token).and_return(「a_token」) captcha = mock(MathCaptcha) captcha.should_receive(:display_string).and_return(「display string」) captcha.should_receive(:token).and_return(@token) assigns[:captcha] = captcha render 「/recipes/new」 response.should have_tag(「form」) do with_tag 「input[name *= captcha_value]」 with_tag 「input[name *= token]」 end end it 「should display captcha」 do @token = mock_model(Token) @token.should_receive(:token).and_return(「a_token」) captcha = mock(MathCaptcha) captcha.should_receive(:display_string).and_return(「display string」) captcha.should_receive(:token).and_return(@token) assigns[:captcha] = captcha render 「/recipes/new」 response.should have_tag(「form」) do with_tag 「input[name *= captcha_value]」 with_tag 「input[name *= token]」 end end 這個例子和Controller的測試同樣地建立了一個模對象,除了在這個用例中在CAPTCHA中放置了另外的模變量(也就是View將會請求顯示字符串和Token)。CAPTCHA放在assigns的Hash中,這樣View就能夠渲染它了。(重要的安全提示:在啓動渲染以前爲全部變量賦值,不然測試將沒法發現它們。)這個例子中,爲了簡潔我只測試了新增的部分。在實際的測試中,你可能但願同時測試一下原來的東西是否是還在。 測試Helper RSpec容許從View中脫離出來單獨測試Helper函數。Helper測試文件放在spec/helpers目錄下,文件名和描述對象都須要與被測試的Helper想對應。例如,在application_helper.rb文件中有這樣的函數: Ruby代碼 def inflect(singular, count, plural = nil) plural ||= singular.pluralize if count == 1 then singular else plural end end def inflect(singular, count, plural = nil) plural ||= singular.pluralize if count == 1 then singular else plural end end 在spec/helpers/application_helper_spec.rb中應該有這樣的測試: Ruby代碼 require File.dirname(__FILE__) + ‘/../spec_helper’ describe ApplicationHelper do it 「should inflect a word」 do inflect(「banana」, 3).should == 「bananas」 inflect(「banana」, 1).should == 「banana」 inflect(「is」, 2, 「are」).should == 「are」 end end require File.dirname(__FILE__) + ‘/../spec_helper’ describe ApplicationHelper do it 「should inflect a word」 do inflect(「banana」, 3).should == 「bananas」 inflect(「banana」, 1).should == 「banana」 inflect(「is」, 2, 「are」).should == 「are」 end end 把要測試的Helper類做爲describe的參數,全部這個Helper類裏面的函數的測試放在裏面的行爲中。這意味着,你不須要任何引用就能夠直接使用這個Helper函數,就像例子裏調用inflect函數這樣。 沒有什麼辦法在Helper測試中獲得模板或者Controller對象,這也許會出現些難題,好比當你想測試一段直接經過concat將純文本直接鏈接到模板中的代碼時。我建議你將文本生成函數與文本插入函數分開,這樣能夠方便測試,或者在View測試中測試這些Helper。 一些現有的Test::Unit pluging與RSpec有衝突,因此你須要在運行他們以前將你的spec目錄刪除。