用Ruby實(shí)現(xiàn)一個(gè)單元測(cè)試框架的教程
在去年的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)文章
實(shí)例解析Ruby設(shè)計(jì)模式開(kāi)發(fā)中對(duì)觀察者模式的實(shí)現(xiàn)
這篇文章主要介紹了實(shí)例解析Ruby設(shè)計(jì)模式開(kāi)發(fā)中對(duì)觀察者模式的實(shí)現(xiàn),Ruby中自帶的observer類(lèi)自然是絕佳的使用示例,需要的朋友可以參考下2016-04-04采用UTF-8解決Ruby on Rails程序的中問(wèn)題
將.rb文件和.rhtml文件都保存為utf-8格式2008-12-12Ruby on Rails基礎(chǔ)之新建項(xiàng)目
Ruby on Rails 是一個(gè)可以使你開(kāi)發(fā),部署,維護(hù) web 應(yīng)用程序變得簡(jiǎn)單的框架。下面我們就來(lái)看看如何簡(jiǎn)單便捷的使用這一框架,本系列文章將一一為大家揭秘2016-02-02在Ruby on Rails中優(yōu)化ActiveRecord的方法
這篇文章主要介紹了在Ruby on Rails中優(yōu)化ActiveRecord的方法,本文來(lái)自于IBM官方網(wǎng)站技術(shù)文檔,需要的朋友可以參考下2015-04-04