欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

用Ruby實(shí)現(xiàn)一個(gè)單元測(cè)試框架的教程

 更新時(shí)間:2015年04月03日 16:42:58   作者:唐尤華  
這篇文章主要介紹了用Ruby實(shí)現(xiàn)一個(gè)單元測(cè)試框架的教程,在檢測(cè)Ruby代碼bug的時(shí)候非常有用,需要的朋友可以參考下

在去年的YOW Melbourne開(kāi)發(fā)者大會(huì)上,我參加了一些研習(xí)班。這些研習(xí)班由@coreyhaines@rains負(fù)責(zé),因此TDD(測(cè)試驅(qū)動(dòng)開(kāi)發(fā))成為了主要討論的內(nèi)容。通常這不是一個(gè)問(wèn)題,但是令人沮喪的是(考慮到這是2010年舉辦的開(kāi)發(fā)者大會(huì)),那時(shí)上網(wǎng)還不是很方便,我剛裝上linux的筆記本無(wú)法下載Rspec。幸運(yùn)的是幾周前,我決定自己寫(xiě)一個(gè)單元測(cè)試框架(因?yàn)槲矣羞@個(gè)能力:)),接著我就有了一個(gè)可用的測(cè)試框架,問(wèn)題解決了。但是,這讓我想到一個(gè)問(wèn)題,最少可以用多少代碼寫(xiě)成一個(gè)可用的單元測(cè)試框架?

一個(gè)最小可用的單元測(cè)試

剛開(kāi)始寫(xiě)一個(gè)單元測(cè)試框架的時(shí)候代碼是很少的,但當(dāng)我想給它加入一些特性時(shí)就變得沒(méi)有那么精煉了:) 幸運(yùn)的是重寫(xiě)是很容易的。我們真正需要做的是執(zhí)行下面的代碼:
 

describe "some test" do
 it "should be true" do
  true.should == true
 end
 
 it "should show that an expression can be true" do
  (5 == 5).should == true
 end
 
 it "should be failing deliberately" do
  5.should == 6
 end
end

正如你看到的,它很像是一個(gè)基本的Rspec測(cè)試。讓我們寫(xiě)一些代碼來(lái)執(zhí)行它。

譯注:RSpec 工具是一個(gè) Ruby 軟件包,可以用它構(gòu)建有關(guān)您的軟件的規(guī)范。該規(guī)范實(shí)際上是一個(gè)描述系統(tǒng)行為的測(cè)試。

構(gòu)建一個(gè)簡(jiǎn)單的框架

首先要做的是使用“describe”來(lái)定義一個(gè)新的測(cè)試。既然我們想要把”describe” block放在任何地方(例如,文件本身),我們需要對(duì)Ruby做一點(diǎn)擴(kuò)展?!皃uts”函數(shù)在Kernel block中,因此可以在任何地方使用(因?yàn)镺bject類(lèi)包含了Kernel并且Ruby中的每個(gè)對(duì)象都繼承自O(shè)bject類(lèi)),同樣的我們會(huì)把describe放到Kernel block中以賦予同樣的能力):
 

module Kernel
 def describe(description, &block)
  tests = Dsl.new.parse(description, block)
  tests.execute
 end
end

譯注:Ruby block:Ruby語(yǔ)言的block功能類(lèi)似回調(diào)函數(shù)。

正如你看到的,”describe”接收一個(gè)用來(lái)描述測(cè)試的字符串和包含了測(cè)試代碼的block。在這里,我們將測(cè)試的代碼和”describe”分開(kāi)講解(例如,”it” block)。因此我們創(chuàng)建了Dsl類(lèi),用它的parse函數(shù)處理待測(cè)試的block,結(jié)果會(huì)產(chǎn)生一個(gè)可以執(zhí)行我們所有測(cè)試的對(duì)象,但是不要高興得太早。Dsl類(lèi)看上去是這樣的:

class Dsl
 def initialize
  @tests = {}
 end
 def parse(description, block)
  self.instance_eval(&block)
  Executor.new(description, @tests)
 end
 def it(description, &block)
  @tests[description] = block
 end
end

這里要做的是在Dsl對(duì)象的上下文里對(duì)block求值:
 

self.instance_eval(&block)

我們的Dsl對(duì)象有一個(gè)”it”函數(shù),同樣也接收一個(gè)描述和一個(gè)block,這里和describe block包含的內(nèi)容完全一致,一切都運(yùn)行得很好(例如,我們基本上會(huì)在幾個(gè)函數(shù)調(diào)用時(shí)使用”it”函數(shù),每次都傳入一個(gè)描述和一個(gè)block)。我們還可以在Dsl對(duì)象中定義其他的函數(shù),并且這些函數(shù)會(huì)成為允許在”describe” block中使用的“語(yǔ)言”的一部分)。

在describe block中,”it”函數(shù)會(huì)為每個(gè)”it” block調(diào)用一次。每次調(diào)用時(shí),會(huì)把輸入的block以測(cè)試描述作為鍵值存儲(chǔ)在哈希表中。完成這些以后,我們只要?jiǎng)?chuàng)建一個(gè)Executor對(duì)象,可以對(duì)我們所有的測(cè)試block進(jìn)行迭代,調(diào)用它們并產(chǎn)生執(zhí)行結(jié)果。Executor代碼如下:
 

class Executor
 def initialize(description, tests)
  @description = description
  @tests = tests
  @success_count = 0
  @failure_count = 0
 end
 def execute
  puts "#{@description}"
  @tests.each_pair do |name, block|
   print " - #{name}"
   result = self.instance_eval(&block)
   result ? @success_count += 1 : @failure_count += 1
   puts result ? " SUCCESS" : " FAILURE"
  end
  summary
 end
 def summary
  puts "\n#{@tests.keys.size} tests, #{@success_count} success, #{@failure_count} failure"
 end
end

我們的executor代碼非常簡(jiǎn)單。輸出”describe” block的描述,然后遍歷所有存儲(chǔ)的”it” block并且在executor對(duì)象中執(zhí)行它們。這么處理沒(méi)有什么特別原因,但這意味著executor對(duì)象同樣也可以包含其他函數(shù),并且可以在”it” block中作為一種“語(yǔ)言”來(lái)使用(比如,我們dsl的一部分可以定義為executor的一個(gè)函數(shù))。譬如,我們可以在executor上定義下列函數(shù):
 

def should_be_five(x)
 5 == x
end

這個(gè)函數(shù)同樣可以在”it” block內(nèi)部使用,但對(duì)于我們這個(gè)簡(jiǎn)單的測(cè)試沒(méi)有這個(gè)必要。

所以,”it” block會(huì)計(jì)算并存儲(chǔ)結(jié)果,通常結(jié)果只是”it” block最后一個(gè)語(yǔ)句的返回值(按照常規(guī)的Ruby)。這里,我們希望確保最后一個(gè)語(yǔ)句總是返回一個(gè)布爾值(標(biāo)明測(cè)試通過(guò)或失?。?,通過(guò)它我們可以輸出一些有意義提示。

我們還差最后一步,”should”函數(shù)代碼如下:
 

true.should == true
5.should == 5

每個(gè)對(duì)象都應(yīng)當(dāng)提供自己”should”函數(shù),代碼如下:
 

class Object
 def should
  self
 end
end

這個(gè)函數(shù)并沒(méi)有真正做什么工作(僅僅是返回對(duì)象本身);它僅僅是一個(gè)讓測(cè)試讀起來(lái)更好的語(yǔ)法。

在這個(gè)階段,我們只是將測(cè)試計(jì)算的結(jié)構(gòu)轉(zhuǎn)換成一個(gè)字符串,表明測(cè)試結(jié)果通過(guò)或失敗并輸出。在這個(gè)過(guò)程中,我們會(huì)統(tǒng)計(jì)通過(guò)或失敗的測(cè)試數(shù)量,所以可以在最后給出一個(gè)總結(jié)報(bào)告。這就是我們所需要的所有的代碼,如果我們將他們放到一起,就是下面的44行代碼:

module Kernel
 def describe(description, &block)
  tests = Dsl.new.parse(description, block)
  tests.execute
 end
end
class Object
 def should
  self
 end
end
class Dsl
 def initialize
  @tests = {}
 end
 def parse(description, block)
  self.instance_eval(&block)
  Executor.new(description, @tests)
 end
 def it(description, &block)
  @tests[description] = block
 end
end
class Executor
 def initialize(description, tests)
  @description = description
  @tests = tests
  @success_count = 0
  @failure_count = 0
 end
 def execute
  puts "#{@description}"
  @tests.each_pair do |name, block|
   print " - #{name}"
   result = self.instance_eval(&block)
   result ? @success_count += 1 : @failure_count += 1
   puts result ? " SUCCESS" : " FAILURE"
  end
  summary
 end
 def summary
  puts "\n#{@tests.keys.size} tests, #{@success_count} success, #{@failure_count} failure"
 end
end

如果我們“需要”使用這個(gè)框架執(zhí)行最初的那個(gè)測(cè)試,我們會(huì)得到下面輸出結(jié)果:

    some test

    - should be true SUCCESS

    - should show that an expression can be true SUCCESS

    - should be failing deliberately FAILURE

    3 tests, 2 success, 1 failure

太好了!現(xiàn)在,如果你因沒(méi)有一個(gè)單元測(cè)試框架而煩惱并且不想莽撞地寫(xiě)代碼,只要花上5分鐘你就可以得到一個(gè)能夠助你一臂之力的測(cè)試框架。當(dāng)然,這里有一些略微夸大;你很快就會(huì)想到這里缺少額外的驗(yàn)證API、更好的輸出、對(duì)象仿真和測(cè)試樁等等。然而,我們可以很容易的在精簡(jiǎn)的框架上擴(kuò)展其中的一些功能(例如,增加額外的DSL元素)——只消花費(fèi)很小的努力。如果你不相信我,可以看看bacon ,它只用了幾百行代碼就完成了Rspec一個(gè)精簡(jiǎn)版。我編寫(xiě)的Attest測(cè)試框架是另一個(gè)很好的例子(這么說(shuō)有自賣(mài)自夸的嫌疑:P)。這兩者都缺少任何內(nèi)建的test double 支持,我會(huì)在另外一個(gè)時(shí)間討論如何添加test double支持

譯注:Test Double:在對(duì)象編程中“自動(dòng)化單元測(cè)試”的專(zhuān)業(yè)術(shù)語(yǔ),涵蓋的類(lèi)型有Test Stub(測(cè)試樁)、Mock Object、Test Spy、Fake Object和Dummy Object。

相關(guān)文章

最新評(píng)論