麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 編程 > Ruby > 正文

對Ruby on Rails進行高效的單元測試的教程

2020-02-24 15:37:26
字體:
來源:轉載
供稿:網(wǎng)友

我們在開發(fā)系統(tǒng)的時候需要對大量的數(shù)據(jù)進行分析,這不僅需要準確的數(shù)據(jù)分析,而且需要一定的速度,下文是武林技術頻道小編和大家分享的對Ruby on Rails進行高效的單元測試的教程。

  • ??? 改變思路:能做到從需求到代碼的過程轉換,逐步細化;
  • ??? 簡化代碼:力圖讓每個方法都很小,只專注一件事;
  • ??? 優(yōu)化代碼:當測試代碼寫不出來,或者需要寫很長的時候,說明代碼是有問題的,是可以被分解的,需要進一步優(yōu)化;
  • ??? 便于擴展:當擴展新業(yè)務或修改舊業(yè)務時,如果測試代碼沒有成功,則說明擴展和修改不成功;
  • ??? 時半功倍:貌似寫測試代碼很費時,實際在測試、部署和后續(xù)擴展中,測試代碼將節(jié)省更多的時間。

環(huán)境搭建

筆者采用的測試環(huán)境是比較流行通用的框架:RSpec + Factory Girl,并用autotest自動工具。RSpec是一種描述性語言,通過可行的例子描述系統(tǒng)行為,非常容易上手,測試用例非常容易理解。Factory Girl可以很好的幫助構造測試數(shù)據(jù),免去了自己寫fixture的煩惱。Autotest能自動運行測試代碼,隨時檢測測試代碼的結果,并且有很多的插件支持,可以讓測試結果顯示的很炫。
第一步 安裝rspec和rspec-rails

在命令行中執(zhí)行如下命令:

$ sudo gem install rspec v = 1.3.0$ sudo gem install rspec-rails v = 1.3.2

安裝完成后,進入rails應用所在的目錄,運行如下腳本,生成spec測試框架:

$ script/generate rspec       exists lib/tasks identical lib/tasks/rspec.rake identical script/autospec identical script/spec  exists spec identical spec/rcov.opts identical spec/spec.opts identical spec/spec_helper.rb

第二步 安裝factory-girl


在命令行中執(zhí)行如下命令:

$ sudo gem install rspec v = 1.3.0$ sudo gem install rspec-rails v = 1.3.2

安裝完成后,進入rails應用所在的目錄,運行如下腳本,生成spec測試框架:

$ script/generate rspec       exists lib/tasks identical lib/tasks/rspec.rake identical script/autospec identical script/spec  exists spec identical spec/rcov.opts identical spec/spec.opts identical spec/spec_helper.rb

第二步 安裝factory-girl

在命令行中執(zhí)行如下命令:

$ sudo gem install factory-girl

在config/environment/test.rb中,加入factory-girl這個gem:

ruby.html="" tags="">ruby;">config.gem "factory_girl"

在spec/目錄下,增加一個factories.rb的文件,用于所有預先定義的model工廠。
第三步 安裝autotest

在命令行中執(zhí)行如下命令:

$ sudo gem install ZenTest$ sudo gem install autotest-rails

然后設置與RSpec的集成,在rails應用的目錄下,運行如下的命令,就可以顯示測試用例的運行結果。

RSPEC=true autotest or autospec

在自己的home目錄下,增加一個.autotest設置所有的Rails應用的autotest插件。當然,也可以把這個文件加到每個應用的根目錄下,這個文件將覆蓋home目錄下的文件設置。autotest的插件很多,筆者用到如下的plugin:

$ sudo gem install autotest-growl$ sudo gem install autotest-fsevent$ sudo gem install redgreen

設置.autotest文件,在.autotest中,加入如下代碼。

require 'autotest/growl' require 'autotest/fsevent' require 'redgreen/autotest' Autotest.add_hook :initialize do |autotest| %w{.git .svn .hg .DS_Store ._* vendor tmp log doc}.each do |exception|  autotest.add_exception(exception) endend

測試經(jīng)驗

安裝了必要的程序庫以后,就可以寫測試代碼了。本例中,所有應用都是在Rails 2.3.4上開發(fā)的,RSpec采用的是1.3.0的版本。為了很好的說明問題,我們假定這樣的需求:判斷一個用戶在一個時間段內(nèi)是否遲到。寫測試代碼時都是遵循一個原則,只關心輸入和輸出,具體的實現(xiàn)并不在測試代碼的考慮范圍之內(nèi),是行為驅(qū)動開發(fā)。根據(jù)這個需求,我們將會設計方法absence_at(start_time,end_time),有兩個輸入值start_time和end_time以及一個輸出值,類型是boolean。對應的測試代碼如下:

describe "User absence or not during [start_time,end_time]" do before :each do   @user = Factory(:user) end it "should return false when user not absence " do  start_time = Time.utc(2010,11,9,12,0,0,0)  end_time = Time.utc(2010,11,9,12,30,0)   @user.absence_at(start_time,end_time).should be_false end it "should return true when user absence " do  start_time = Time.utc(2010,11,9,13,0,0,0)  end_time = Time.utc(2010,11,9,13,30,0)   @user.absence_at(start_time,end_time).should be_ture endend

測試代碼已經(jīng)完成。至于absence_at方法我們并不關心它的實現(xiàn),只要這個方法的結果能讓測試代碼運行結果正確就可以。在此測試代碼的基礎上,就可以大膽地去完成代碼,并根據(jù)測試代碼的結果不斷修改代碼直到所有測試用例通過。
Stub的使用

寫測試代碼,最好首先從model開始。因為model的方法能很好與輸入輸出的原則吻合,容易上手。最初的時候,你會發(fā)現(xiàn)mock和stub很好用,任何的對象都可以mock,并且在它的基礎上可以stub一些方法,省去構造數(shù)據(jù)的麻煩,一度讓筆者覺得測試代碼是如此美麗,一步步的深入,才發(fā)現(xiàn)自己陷入了stub的誤區(qū)。還是引用上面的例子,我們的代碼實現(xiàn)如下:

class User < ActiveRecord::Base def absence_at(start_time,end_time)    return false if have_connection_or_review?(start_time,end_time)  return (login_absence_at?(start_time,end_time) ? true : false)   endend

按照最初寫測試代碼的思路,本方法中存在三種情況,即需要三個用例,而且還調(diào)用了其他兩個方法,需要對他們進行stub,于是就有了下面的測試代碼。記得當時完成后還很興奮,心中還想:這么寫測試代碼真有趣。

before(:each) do @user = User.newenddescribe "method <absence_at(start_time,end_time)>" do  s = Time.now e = s + 30.minutes # example one it "should be false when user have interaction or review" do  @user.stub!(:have_connection_or_review?).with(s,e).and_return(true)  @user.absence_at(s,e).should be_false end   # example two it "should be true when user has no interaction and he no waiting at platform" do  @user.stub!(:have_connection_or_review?).with(s,e).and_return(false)  @user.stub!(:login_absence_at?).with(s,e).and_return(true)  @user.absence_at(s,e).should be_true end # example three it "should be false when user has no interaction and he waiting at platform" do  @user.stub!(:have_connection_or_review?).with(s,e).and_return(false)  @user.stub!(:login_absence_at?).with(s,e).and_return(false)  @user.absence_at(s,e).should be_false end  end

上面的測試代碼,是典型把代碼的實現(xiàn)細節(jié)帶到了測試代碼中,完全是本末倒置的。當然這個測試代碼運行的時候,結果都是正確的。那是因為用stub來假定所有的子方法都是對的,但是如果這個子方法have_connection_or_review?發(fā)生變化,它不返回boolean值,那么將會發(fā)生什么呢?這個測試代碼依然正確,可怕吧!這都沒有起到測試代碼的作用。

另外,如果是這樣,我們不僅要修改have_connection_or_review?的測試代碼,而且還要修改absence_at的測試代碼。這不是在增大代碼維護量嗎?

相比而言,不用stub的測試代碼,不用修改,如果Factory的數(shù)據(jù)沒有發(fā)生變化,那么測試代碼的結果將是錯誤的,因為have_connection_or_review?沒有通過測試,導致absence_at方法無法正常運行。

其實stub主要是mock一些本方法或者本應用中無法得到的對象,比如在tech_finish?方法中,調(diào)用了一個file_service來獲得Record對象的所有文件,在本方法測試代碼運行過程中,無法得到這個service,這時stub就起作用了:

class A < ActiveRecord::Base has_many :records def tech_finish?  self.records.each do |v_a|   return true if v_a.files.size == 5  end  return false endendclass Record < ActiveRecord::Base belongs_to :a has_files # here is a service in gemend

所對應的測試代碼如下:

describe "tech_finish?" do it "should return true when A's records have five files" do  record = Factory(:record)  app = Factory(:a,:records=>[record])  record.stub!(:files).and_return([1,2,3,4,5])     app.tech_finish?.should == true end it "should return false when A's records have less five files" do  record = Factory(:record)  app = Factory(:a,:records=>[record])  record.stub!(:files).and_return([1,2,3,5])     app.tech_finish?.should == false endend

Factory的使用

有了這個工廠,可以很方便的構造不同的模擬數(shù)據(jù)來運行測試代碼。還是上面的例子,如果要測試absence_at方法,涉及到多個model:

  • ??? HistoryRecord:User的上課記錄
  • ??? Calendar:User的課程表
  • ??? Logging:User的日志信息

如果不用factory-girl構造測試數(shù)據(jù),我們將不得不在fixture構造這些測試數(shù)據(jù)。在fixture構造的數(shù)據(jù)無法指定是那個測試用例使用,但是如果用Factory的話,可以為這個方法專門指定一組測試數(shù)據(jù)。

Factory.define :user_absence_example,:class => User do |user| user.login "test" class << user  def default_history_records   [Factory.build(:history_record,:started_at=>Time.now),    Factory.build(:history_record,:started_at=>Time.now)]  end  def default_calendars   [Factory.build(:calendar),    Factory.build(:calendar)]         end   def default_loggings   [Factory.build(:logging,:started_at=>1.days.ago),    Factory.build(:logging,:started_at=>1.days.ago)]   end  end  user.history_records {default_history_records}  user.calendars {default_calendars}  user.loggings {default_loggings}end

這個測試數(shù)據(jù)的構造工廠,可以放在factories.rb文件中,方便其他測試用例使用,也可以直接放到測試文件的before中,僅供本測試文件使用。通過factory的構造,不僅可以為多個測試用例共享同一組測試數(shù)據(jù),而且測試代碼也簡潔明了。

before :each do @user = Factory.create(:user_absence_example)end

Readonly的測試

在筆者的系統(tǒng)中,大量使用了acts_as_readonly,從另外一個數(shù)據(jù)庫來讀取數(shù)據(jù)。由于這些model并不在本系統(tǒng)中,所以當用Factory構造測試數(shù)據(jù)的時候,總會有問題。雖然也可以使用mock來達到這個目的,但是由于mock的局限性,還是無法靈活的滿足構造測試數(shù)據(jù)的需要。為此,擴展了一些代碼,使得這些model依然可以測試。核心思想則是,根據(jù)配置文件的設置,將對應的readonly的表創(chuàng)建在測試數(shù)據(jù)庫,這個操作在運行測試之前執(zhí)行,這樣就達到與其他model一樣的效果。site_config配置文件中,關于readonly的配置格式如下:

readonly_for_test: logings:  datetime: created_at  string: status  integer: trainer_id

Gem的測試

Gem在Rails中被廣泛使用,而且是最基礎的東西,因此它的準確無誤就顯得更加重要。在不斷實踐的基礎上,筆者所在的團隊總結出一種用spec測試gem的方法。假設我們要測試的gem是platform_base,步驟如下:

1. 在gem的根目錄下創(chuàng)建一個目錄spec(路徑為platform_base/spec)。

2. 在gem的根目錄下創(chuàng)建文件Rakefile(路徑為platform_base/Rakefile),內(nèi)容如下:

require 'rubygems'require 'rake'require 'spec/rake/spectask'Spec::Rake::SpecTask.new('spec') do |t| t.spec_opts = ['--options', "spec/spec.opts"] t.spec_files = FileList['spec/**/*_spec.rb']end

3. 文件在spec目錄下創(chuàng)建spec.opts(路徑為platform_base/spec/spec.opts),內(nèi)容如下:

?

復制代碼 代碼如下:
--colour
--format progress
--loadby mtime
--reverse

?

4. 在spec目錄下,創(chuàng)建一個Rails app,名為test_app。這個新應用需要有spec目錄和spec_helper.rb文件。

5. 為了保持簡化,把這個新app(test_app)整理一下,刪除vendor和public目錄,最終的結構如下:

?

復制代碼 代碼如下:
test_app
?? |- app
?? |- config
?? |?? |- environments
?? |?? |- initializers
?? |?? |- app_config.yml
?? |?? |- boot.rb
?? |?? |- database.yml
?? |?? |- environment.rb
?? |?? /- routes.rb
?? |- db
?? |?? /- test.sqlite3
?? |- log
?? /- spec
?????? /- spec_helper.rb

?

6. 在config/environment.rb配置文件中,增加如下代碼:

Rails::Initializer.run do |config| config.gem 'rails_platform_base'end

7. 在platform_base/spec/目錄下增加helpers_spec.rb文件,內(nèi)容如下:

require File.join(File.dirname(__FILE__), 'test_app/spec/spec_helper')

describe "helpers" do describe "url_of" do  before do   Rails.stub!(:env).and_return("development")   @controller = ActionController::Base.new  end  it "should get url from app's configration" do   @controller.url_of(:article, :comments, :article_id => 1).should == "http://www.idapted.com/article/articles/1/comments"   @controller.url_of(:article, :comments, :article_id => 1, :params=>{:category=>"good"}).should == "http://www.idapted.com/article/articles/1/comments?category=good"  end endend

至此,準備工作已經(jīng)就緒,可以在platform_base目錄下,運行rake spec來進行測試,當然現(xiàn)在什么都不會發(fā)生,因為還沒有測試代碼呢。本方法中,最關鍵的就是下面的require語句,不僅加載了Rails environment,而且把gem在test_app中使用并測試。

require File.join(File.dirname(__FILE__), 'test_app/spec/spec_helper')

Controller的測試

對于controller的測試,一般來說比較簡單,基本是三段式:初始化參數(shù)、請求方法、返回render或者redirect_to。如下例中,對某個controller的index方法的測試:

describe "index action" do it "should render report page with the current month report" do  controller.stub!(:current_user).and_return(@user)  get :index,{:flag => “test”}  response.should render_template("index") endend

有些controller會設置session或者flash,這時的測試代碼就一定要檢查這個值設置的是否正確,而且還需要增加測試用例來覆蓋不同的值,這樣才能對方法進行全面的測試。如下例:

describe "create action" do it "should donot create new user with wrong params" do  post :create  response.should redirect_to(users_path)  flash[:notice].should == "Create Fail!" end it "should create a new user with right params" do  post :create, {:email => "[email protected]"}  response.should redirect_to(users_path)  flash[:notice].should == "Create Successful!" endend

同時,也需要對controller的assigns進行測試,以保證返回正確的數(shù)據(jù)。如下例:

before(:each) do @course = Factory(:course)end describe "show action" do it "should render show page when flag != assess and success" do   get :show, :id => @course.id, :flag =>"test"  response.should render_template("show")  assigns[:test_paper].should == @course  assigns[:flag].should == "test" end it "should render show page when flag == assess and success" do  get :show, :id => @course.id, :flag =>"assess"  response.should render_template("show")  assigns[:test_paper].should == @course  assigns[:flag].should == "assess" end  end

View的測試

View的測試代碼寫的比較少,基本上是把核心的view部分集成到controller中來測試。主要用integrate_views方法。如下例:

describe AccountsController do integrate_views describe "index action" do  it "should render index.rhtml" do   get :index   response.should render_template("index")   response.should have_tag("a[href=?]",new_account_path)   response.should have_tag("a[href=?]",new_session_path)  end endend

在編寫測試代碼的時候,其實不必做任何詳細的工作。一些簡單的方法和Rails內(nèi)部方法,完全不需要測試,希望武林技術頻道介紹的知識能幫到您!?

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表

圖片精選

主站蜘蛛池模板: 久久99国产精品久久99果冻传媒 | 87成人免费看片 | 久久一区国产 | av免费在线网站 | 精品免费久久 | 巨乳激情| 在线成人精品视频 | 国产成人自拍av | 欧美四级在线观看 | 欧美成人免费 | 成人做爰高潮片免费视频韩国 | 91精品久久久久久 | 成年人观看免费视频 | 一级啪啪片| av在线不卡免费 | 久久久综 | 成人不卡在线观看 | 日韩a毛片免费观看 | 蜜桃视频在线入口www | 日本在线一区二区 | 免费放黄网站在线播放 | 国产日韩精品欧美一区视频 | 日韩1区| 成人情欲视频在线看免费 | 性大片免费看 | 欧美在线观看禁18 | 91麻豆精品国产91久久久点播时间 | 92精品国产自产在线 | 欧美激情精品久久久久久黑人 | 久久777国产线看观看精品 | 黄色免费电影网址 | 亚洲成人在线免费 | 国产一区二区视频观看 | 成熟女人特级毛片www免费 | 国产精品久久久久久久久久久久久久久 | 91成人影库 | 久久久久电影网站 | 国产精品久久国产精品 | 国产精品区一区二区三区 | av日韩在线免费观看 | 在线免费av网站 |