TDD和BDD
在GitBook上看過(guò)一篇文章亭病,一個(gè)不寫(xiě)單元測(cè)試的程序員不是一個(gè)好的攻城獅鹅很。坦白的說(shuō),在Objective-C這個(gè)領(lǐng)域的里罪帖,我見(jiàn)過(guò)的會(huì)主動(dòng)寫(xiě)單元測(cè)試的程序員還是比較少的促煮。當(dāng)然了,在那些大的開(kāi)源項(xiàng)目里整袁,我還是見(jiàn)到過(guò)很多單元測(cè)試的應(yīng)用菠齿。
于是也就促使我想總結(jié)總結(jié)自己現(xiàn)在對(duì)單元測(cè)試的理解。眾所周知蘋(píng)果在Xcode5
中引入了XCTest
框架替換了原來(lái)的SenTestingKit
坐昙。這也顯示了蘋(píng)果一直致力于在iOS開(kāi)發(fā)中集成更方便可用的測(cè)試绳匀。但是我一直覺(jué)得XCTest
的斷言可讀性較差,如果是讓他人來(lái)閱讀這段單元測(cè)試炸客,會(huì)比較的花費(fèi)精力疾棵。
再進(jìn)入討論單元測(cè)試之前,我們來(lái)談?wù)劜灰粯訙y(cè)試思想
行為驅(qū)動(dòng)開(kāi)發(fā)(英語(yǔ):Behavior-driven development痹仙,縮寫(xiě)B(tài)DD)是一種敏捷軟件開(kāi)發(fā)的技術(shù)是尔,BDD的重點(diǎn)是通過(guò)與利益相關(guān)者的討論取得對(duì)預(yù)期的軟件行為的清醒認(rèn)識(shí)。它通過(guò)用自然語(yǔ)言書(shū)寫(xiě)非程序員可讀的測(cè)試用例擴(kuò)展了測(cè)試驅(qū)動(dòng)開(kāi)發(fā)方法开仰。
測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(英語(yǔ):Test-driven development拟枚,縮寫(xiě)為T(mén)DD)是一種軟件開(kāi)發(fā)過(guò)程中的應(yīng)用方法,由極限編程中倡導(dǎo)抖所,以其倡導(dǎo)先寫(xiě)測(cè)試程序梨州,然后編碼實(shí)現(xiàn)其功能得名痕囱。測(cè)試驅(qū)動(dòng)開(kāi)發(fā)是戴兩頂帽子思考的開(kāi)發(fā)方式:先戴上實(shí)現(xiàn)功能的帽子田轧,在測(cè)試的輔助下,快速實(shí)現(xiàn)其功能鞍恢;再戴上重構(gòu)的帽子傻粘,在測(cè)試的保護(hù)下每窖,通過(guò)去除冗余的代碼,提高代碼質(zhì)量弦悉。測(cè)試驅(qū)動(dòng)著整個(gè)開(kāi)發(fā)過(guò)程:首先窒典,驅(qū)動(dòng)代碼的設(shè)計(jì)和功能的實(shí)現(xiàn);其后稽莉,驅(qū)動(dòng)代碼的再設(shè)計(jì)和重構(gòu)瀑志。
上面講述了TDD和BDD的思想差別,看到這里污秆,你們認(rèn)為當(dāng)前的iOS開(kāi)發(fā)適合怎樣的測(cè)試思想劈猪。不知道你們開(kāi)發(fā)中的實(shí)際情況是如何,在現(xiàn)在大環(huán)境趕進(jìn)度的開(kāi)發(fā)下良拼,一般我是采用BDD的測(cè)試方法战得。
而談到BDD,我要給大家介紹一個(gè)iOS中非常有名并且好用的BDD框架 —— Kiwi庸推。
Kiwi
Kiwi的安裝
- 項(xiàng)目主頁(yè): https://github.com/kiwi-bdd/Kiwi
使用Cocopods 安裝
target :YourProjectTests do
pod 'Kiwi'
end
在這里記得一定要替換YourProject
為你的項(xiàng)目名常侦。
Kiwi的基本結(jié)構(gòu)
在講Kiwi的常用語(yǔ)法前,我們先來(lái)看一段Kiwi的Github提供的示例代碼贬媒。
describe(@"Team", ^{
context(@"when newly created", ^{
it(@"should have a name", ^{
id team = [Team team];
[[team.name should] equal:@"Black Hawks"];
});
it(@"should have 11 players", ^{
id team = [Team team];
[[[team should] have:11] players];
});
});
});
我們很容易根據(jù)上下文將其提取為Given..When..Then的三段式自然語(yǔ)言聋亡。
Given a team, when newly created, it should have a name, and should have 11 players
是不是非常簡(jiǎn)單易懂的語(yǔ)法結(jié)構(gòu)。
describe
描述需要測(cè)試的對(duì)象內(nèi)容际乘,也即我們?nèi)问街械?code>Given杀捻,context
描述測(cè)試上下文,也就是這個(gè)測(cè)試在When
來(lái)進(jìn)行蚓庭,最后it
中的是測(cè)試的本體致讥,描述了這個(gè)測(cè)試應(yīng)該滿足的條件,三者共同構(gòu)成了Kiwi測(cè)試中的行為描述器赞。它們是可以nest的垢袱,也就是一個(gè)Spec文件中可以包含多個(gè)describe
(雖然我們很少這么做,一個(gè)測(cè)試文件應(yīng)該專注于測(cè)試一個(gè)類)港柜;一個(gè)describe
可以包含多個(gè)context
请契,來(lái)描述類在不同情景下的行為;一個(gè)context
可以包含多個(gè)it
的測(cè)試?yán)?/p>
Kiwi還有一些其他的行為描述關(guān)鍵字夏醉,其中比較重要的包括:
beforeAll(aBlock)
- 當(dāng)前scope內(nèi)部的所有的其他block運(yùn)行之前調(diào)用一次afterAll(aBlock)
- 當(dāng)前scope內(nèi)部的所有的其他block運(yùn)行之后調(diào)用一次beforeEach(aBlock)
- 在scope內(nèi)的每個(gè)it之前調(diào)用一次爽锥,對(duì)于context的配置代碼應(yīng)該寫(xiě)在這里afterEach(aBlock)
- 在scope內(nèi)的每個(gè)it之后調(diào)用一次,用于清理測(cè)試后的代碼specify(aBlock)
- 可以在里面直接書(shū)寫(xiě)不需要描述的測(cè)試pending(aString, aBlock)
- 只打印一條log信息畔柔,不做測(cè)試氯夷。這個(gè)語(yǔ)句會(huì)給出一條警告,可以作為一開(kāi)始集中書(shū)寫(xiě)行為描述時(shí)還未實(shí)現(xiàn)的測(cè)試的提示靶擦。xit(aString, aBlock)
- 和pending一樣腮考,另一種寫(xiě)法雇毫。因?yàn)樵谡嬲龑?shí)現(xiàn)時(shí)測(cè)試時(shí)只需要將x刪掉就是it,但是pending語(yǔ)意更明確踩蔚,因此還是推薦pending
Kiwi使用實(shí)例
就拿項(xiàng)目中一個(gè)真實(shí)的場(chǎng)景來(lái)說(shuō)棚放,我在寫(xiě)完一個(gè)適配所有iPhone機(jī)型的寬高的類之后,我用Kiwi來(lái)進(jìn)行單元測(cè)試馅闽。
首先我這個(gè)類是這么描述寬高的
//CalculateLayout.h
+ (CGFloat)neu_layoutForAlliPhoneHeight:(CGFloat)height;
+ (CGFloat)neu_layoutForAlliPhoneWidth:(CGFloat)width;
// CalculateLayout.m
+ (CGFloat)layoutForAlliPhoneHeight:(CGFloat)height type:(IPhoneType)type {
CGFloat layoutHeight = 0.0f;
switch (type) {
case iPhone4Type:
layoutHeight = ( height / iPhone6Height ) * iPhone4Height;
break;
case iPhone5Type:
layoutHeight = ( height / iPhone6Height ) * iPhone5Height;
break;
case iPhone6Type:
layoutHeight = ( height / iPhone6Height ) * iPhone6Height;
break;
case iPhone6PlusType:
layoutHeight = ( height / iPhone6Height ) * iPhone6PlusHeight;
break;
default:
break;
}
return layoutHeight;
}
+ (CGFloat)layoutForAlliPhoneWidth:(CGFloat)width type:(IPhoneType)type {
CGFloat layoutWidth = 0.0f;
switch (type) {
case iPhone4Type:
layoutWidth = ( width / iPhone6Width ) * iPhone4Width;
break;
case iPhone5Type:
layoutWidth = ( width / iPhone6Width ) * iPhone5Width;
break;
case iPhone6Type:
layoutWidth = ( width / iPhone6Width ) * iPhone6Width;
break;
case iPhone6PlusType:
layoutWidth = ( width / iPhone6Width ) * iPhone6PlusWidth;
break;
default:
break;
}
return layoutWidth;
}
反正大概意思就是我輸入了一個(gè)寬高飘蚯,他根據(jù)UI給定的設(shè)計(jì)圖,返回給我一個(gè)寬高適配當(dāng)前機(jī)型的寬高福也。
那么我們?nèi)绾蝸?lái)寫(xiě)這個(gè)測(cè)試用例呢.
#import <Kiwi/Kiwi.h>
#import "CalculateLayout.h"
SPEC_BEGIN(CalculateLayoutTests)
describe(@"CalculateLayout", ^{
context(@"when calculate width and height", ^{
CGFloat width = [CalculateLayout neu_layoutForAlliPhoneWidth:375.f];
CGFloat height = [CalculateLayout neu_layoutForAlliPhoneHeight:667.f];
pending_(@"All iPhone Test", ^{
});
it(@"should layout width", ^{
[[theValue(width) should] equal:theValue(320.f)];
});
it(@"should layout height", ^{
[[theValue(height) should] equal:theValue(568.f)];
});
});
});
SPEC_END
我寫(xiě)進(jìn)去的寬高數(shù)值是iPhone6的寬高數(shù)值孝冒,如果用5S的模擬器來(lái)運(yùn)行,將會(huì)返回5S的寬高 320 * 568
當(dāng)我們 com+U 運(yùn)行這段測(cè)試用例時(shí)拟杉。
控制臺(tái)的輸出
+ 'CalculateLayout, when calculate width and height, should layout width' [PASSED]
+ 'CalculateLayout, when calculate width and height, should layout height' [PASSED]
可以看到庄涡,由于有context
的存在,以及其可以嵌套的特性搬设,測(cè)試的流程控制相比傳統(tǒng)測(cè)試可以更加精確穴店。我們更容易把before
和after
的作用區(qū)域限制在合適的地方。
實(shí)際的測(cè)試寫(xiě)在it里拿穴,是由一個(gè)一個(gè)的期望(Expectations)來(lái)進(jìn)行描述的泣洞,期望相當(dāng)于傳統(tǒng)測(cè)試中的斷言,要是運(yùn)行的結(jié)果不能匹配期望默色,則測(cè)試失敗球凰。在Kiwi
中期望都由should
或者shouldNot
開(kāi)頭,并緊接一個(gè)或多個(gè)判斷的的鏈?zhǔn)秸{(diào)用腿宰,大部分常見(jiàn)的是be
或者haveSomeCondition
的形式呕诉。在我們上面的例子中我們使用了should not be nil
和should equal
兩個(gè)期望來(lái)確保字符串賦值的行為正確。其他的期望語(yǔ)句非常豐富吃度,并且都符合自然語(yǔ)言描述甩挫,所以并不需要太多介紹。在使用的時(shí)候不妨直接按照自己的想法來(lái)描述自己的期望椿每,一般情況下在IDE
的幫助下我們都能找到想要的結(jié)果伊者。如果您想看看完整的期望語(yǔ)句的列表,可以參看文檔的這個(gè)頁(yè)面间护。從這一點(diǎn)來(lái)看亦渗,Kiwi可以說(shuō)是一個(gè)非常靈活并具有可擴(kuò)展性的測(cè)試框架。
來(lái)解釋下上面的語(yǔ)法中用到的theValue
.
Kiwi
為我們提供了一個(gè)標(biāo)量轉(zhuǎn)對(duì)象的語(yǔ)法糖汁尺,叫做theValue
法精,在做精確比較的時(shí)候我們可以直接使用例子中直接與320.f或者568.f
做比較這樣的寫(xiě)法來(lái)進(jìn)行對(duì)比。
通過(guò)這樣一個(gè)簡(jiǎn)單的例子,我們基本能掌握Kiwi的語(yǔ)法亿虽,以及Kiwi的使用菱涤。單元測(cè)試的門(mén)其實(shí)很好進(jìn)苞也,但是如何用心的洛勉,動(dòng)腦子的去寫(xiě)單元測(cè)試,則是對(duì)我們程序員莫大的考驗(yàn)哦如迟。
我講的并不完善收毫,也不詳細(xì),就算簡(jiǎn)單記錄自己目前的收獲吧殷勘。