iOS開發——TDD、BDD方法以及Kiwi單元測試框架

TDD和BDD

在GitBook上看過一篇文章,一個不寫單元測試的程序員不是一個好的攻城獅。坦白的說,在Objective-C這個領域的里,我見過的會主動寫單元測試的程序員還是比較少的。當然了,在那些大的開源項目里,我還是見到過很多單元測試的應用。

于是也就促使我想總結總結自己現在對單元測試的理解。眾所周知蘋果在Xcode5中引入了XCTest框架替換了原來的SenTestingKit。這也顯示了蘋果一直致力于在iOS開發中集成更方便可用的測試。但是我一直覺得XCTest的斷言可讀性較差,如果是讓他人來閱讀這段單元測試,會比較的花費精力。

再進入討論單元測試之前,我們來談談不一樣測試思想

  • 行為驅動開發(英語:Behavior-driven development,縮寫BDD)是一種敏捷軟件開發的技術,BDD的重點是通過與利益相關者的討論取得對預期的軟件行為的清醒認識。它通過用自然語言書寫非程序員可讀的測試用例擴展了測試驅動開發方法。

  • 測試驅動開發(英語:Test-driven development,縮寫為TDD)是一種軟件開發過程中的應用方法,由極限編程中倡導,以其倡導先寫測試程序,然后編碼實現其功能得名。測試驅動開發是戴兩頂帽子思考的開發方式:先戴上實現功能的帽子,在測試的輔助下,快速實現其功能;再戴上重構的帽子,在測試的保護下,通過去除冗余的代碼,提高代碼質量。測試驅動著整個開發過程:首先,驅動代碼的設計和功能的實現;其后,驅動代碼的再設計和重構。

上面講述了TDD和BDD的思想差別,看到這里,你們認為當前的iOS開發適合怎樣的測試思想。不知道你們開發中的實際情況是如何,在現在大環境趕進度的開發下,一般我是采用BDD的測試方法。

而談到BDD,我要給大家介紹一個iOS中非常有名并且好用的BDD框架 —— Kiwi。

Kiwi

Kiwi的安裝

使用Cocopods 安裝

target :YourProjectTests do
  pod 'Kiwi'
end

在這里記得一定要替換YourProject為你的項目名。

Kiwi的基本結構

在講Kiwi的常用語法前,我們先來看一段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];
        });
    });
});

我們很容易根據上下文將其提取為Given..When..Then的三段式自然語言。

Given a team, when newly created, it should have a name, and should have 11 players

是不是非常簡單易懂的語法結構。

describe描述需要測試的對象內容,也即我們三段式中的Given,context描述測試上下文,也就是這個測試在When來進行,最后it中的是測試的本體,描述了這個測試應該滿足的條件,三者共同構成了Kiwi測試中的行為描述。它們是可以nest的,也就是一個Spec文件中可以包含多個describe(雖然我們很少這么做,一個測試文件應該專注于測試一個類);一個describe可以包含多個context,來描述類在不同情景下的行為;一個context可以包含多個it的測試例。

Kiwi還有一些其他的行為描述關鍵字,其中比較重要的包括:

  • beforeAll(aBlock) - 當前scope內部的所有的其他block運行之前調用一次

  • afterAll(aBlock) - 當前scope內部的所有的其他block運行之后調用一次

  • beforeEach(aBlock) - 在scope內的每個it之前調用一次,對于context的配置代碼應該寫在這里

  • afterEach(aBlock) - 在scope內的每個it之后調用一次,用于清理測試后的代碼

  • specify(aBlock) - 可以在里面直接書寫不需要描述的測試

  • pending(aString, aBlock) - 只打印一條log信息,不做測試。這個語句會給出一條警告,可以作為一開始集中書寫行為描述時還未實現的測試的提示。

  • xit(aString, aBlock) - 和pending一樣,另一種寫法。因為在真正實現時測試時只需要將x刪掉就是it,但是pending語意更明確,因此還是推薦pending

Kiwi使用實例

就拿項目中一個真實的場景來說,我在寫完一個適配所有iPhone機型的寬高的類之后,我用Kiwi來進行單元測試。

首先我這個類是這么描述寬高的

//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;
}

反正大概意思就是我輸入了一個寬高,他根據UI給定的設計圖,返回給我一個寬高適配當前機型的寬高。

那么我們如何來寫這個測試用例呢.

#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

我寫進去的寬高數值是iPhone6的寬高數值,如果用5S的模擬器來運行,將會返回5S的寬高 320 * 568

當我們 com+U 運行這段測試用例時。

控制臺的輸出

+ 'CalculateLayout, when calculate width and height, should layout width' [PASSED]

+ 'CalculateLayout, when calculate width and height, should layout height' [PASSED]

可以看到,由于有context的存在,以及其可以嵌套的特性,測試的流程控制相比傳統測試可以更加精確。我們更容易把beforeafter的作用區域限制在合適的地方。

實際的測試寫在it里,是由一個一個的期望(Expectations)來進行描述的,期望相當于傳統測試中的斷言,要是運行的結果不能匹配期望,則測試失敗。在Kiwi中期望都由should或者shouldNot開頭,并緊接一個或多個判斷的的鏈式調用,大部分常見的是be或者haveSomeCondition的形式。在我們上面的例子中我們使用了should not be nilshould equal兩個期望來確保字符串賦值的行為正確。其他的期望語句非常豐富,并且都符合自然語言描述,所以并不需要太多介紹。在使用的時候不妨直接按照自己的想法來描述自己的期望,一般情況下在IDE的幫助下我們都能找到想要的結果。如果您想看看完整的期望語句的列表,可以參看文檔的這個頁面。從這一點來看,Kiwi可以說是一個非常靈活并具有可擴展性的測試框架。

來解釋下上面的語法中用到的theValue.

Kiwi為我們提供了一個標量轉對象的語法糖,叫做theValue,在做精確比較的時候我們可以直接使用例子中直接與320.f或者568.f做比較這樣的寫法來進行對比。

通過這樣一個簡單的例子,我們基本能掌握Kiwi的語法,以及Kiwi的使用。單元測試的門其實很好進,但是如何用心的,動腦子的去寫單元測試,則是對我們程序員莫大的考驗哦。

我講的并不完善,也不詳細,就算簡單記錄自己目前的收獲吧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內容

  • 我們為什么要用測試框架呢?當然對項目開發有幫助了,但是業內現狀是經常趕進度,所以TDD還是算了吧,BDD就測測數據...
    CrespoXiao閱讀 14,422評論 9 60
  • 哭鬧著要走的人,都不會真正離開。真正想要離開的那個人,挑一個風和日麗的下午,穿上一件大衣出門,消失在秋日的陽光里,...
    苦行尼閱讀 181評論 0 0
  • 這幾天有些頹廢、迷茫,日記好些天沒有寫了,今天七夕;做好了十幾枚竼文佛像印章。 其實這幾天也沒有特別忙,就是老毛病...
    王益軍閱讀 204評論 0 0
  • 1. 環境 Windows 7 Visual Studio 2017 2. Build OPC UA-.NET 1...
    YZUA閱讀 5,604評論 0 2