1、什么是單元測試
單元測試又稱為模塊測試仆救,是指對軟件中的最小可測試單元進行檢查和驗證抒和。是開發(fā)者編寫的一小段代碼,用于檢驗被測代碼的一個很小的彤蔽、很明確的功能是否正確摧莽。
1、單元測試的本質
-
是一種驗證行為
單元測試在開發(fā)前期檢驗了代碼邏輯的正確性顿痪,開發(fā)后期范嘱,無論是修改代碼內部抑或重構,測試的結果為這一切提供了可量化的保障员魏。
-
是一種設計行為
為了可進行單元測試丑蛤,尤其是先寫單元測試(TDD),我們將從調用者思考撕阎,從接口上思考受裹,我們必須把程序單元設計成接口功能劃分清晰的,易于測試的虏束,且與外部模塊耦合性盡可能小棉饶。
-
是一種快速回歸的方式
在原代碼基礎上開發(fā)及修改功能時,單元測試是一種快捷镇匀,可靠的回歸照藻。
-
是程序優(yōu)良的文檔
從效果上而言,單元測試就像是能執(zhí)行的文檔汗侵,說明了在你用各種條件調用代碼時幸缕,你所能期望這段代碼完成的功能群发。
2、在什么時候我們需要單元測試
- 當發(fā)現自己改了代碼之后发乔,經常需要手動驗證的時候
- 改了代碼之后, 怕影響其他地方的bug
- 多人合作項目熟妓,可能交給其他人不放心別人改你的代碼的時候
以上都可以通過寫測試案例,進行自動化測試栏尚,從而減少bug起愈。
3、單元測試的目的
-
保證代碼的質量
代碼可以通過編譯器檢查語法的正確性译仗,卻不能保證代碼邏輯是正確的抬虽,尤其包含了許多單元分支的情況下,單元測試可以保證代碼的行為和結果與我們的預期和需求一致纵菌。在測試某段代碼的行為是否和你的期望一致時阐污,你需要確認,在任何情況下产艾,這段代碼是否都和你的期望一致,譬如參數可能為空滑绒,可能的異步操作等闷堡。
-
保證代碼的可維護性
保證原有單元測試正確的情況下,無論如何修改單元內部代碼疑故,測試的結果應該是正確的杠览,且修改后不會影響到其他的模塊。
-
保證代碼的可擴展性
為了保證可行的可持續(xù)的單元測試纵势,程序單元應該是低耦合的踱阿,否則,單元測試將難以進行钦铁。
2软舌、單元測試的兩種設計思想
1、 測試驅動開發(fā) Test Driven Development(TDD)
先寫測試程序牛曹,然后編碼實現其功能佛点。
測試驅動開發(fā)是戴兩頂帽子思考的開發(fā)方式:先戴上實現功能的帽子,在測試的輔助下黎比,快速實現其功能超营;再戴上重構的帽子,在測試的保護下阅虫,通過去除冗余的代碼演闭,提高代碼質量。
2颓帝、 行為驅動開發(fā) Behavior Driven Development(BDD)
它通過用自然語言書寫非程序員可讀的測試用例擴展了 測試驅動開發(fā)方法(TDD)米碰。這讓開發(fā)者得以把精力集中在代碼應該怎么寫窝革,而不是技術細節(jié)上,而且也最大程度的減少了將代碼編寫者的技術語言與商業(yè)客戶见间、用戶聊闯、利益相關者、項目管理者等的領域語言之間來回翻譯的代價米诉。
例如現在比較流行的框架kiwi就是行為驅動開發(fā)菱蔬。下文會簡單展示kiwi的使用和設計思想。
3史侣、使用Xcode編寫第一個測試案例拴泌。
1、 創(chuàng)建單元測試Target
如上圖所視,在創(chuàng)建項目的時候就可以選擇是否添加單元測試惊橱。如果創(chuàng)建項目的時候沒有添加蚪腐,也可以通過
file - new - target - Test - Unit Testing bundle 來創(chuàng)建
2、 單元測試類介紹
如上圖所示税朴,其實Xcode已經說的很清楚了回季。
-
setUp
用于做一些初始化代碼 -
TearDown
用于對象的銷毀 - 所有測試類都必須繼承
XCTestCase
- 測試方法必須以testXXX開頭,Xcode會自動識別出所有的測試方法
- 在一個類中測試方法的調用順序是按照方法的順序來調用的
調用
- 執(zhí)行所有測試方法:command + u
- 只執(zhí)行某個測試方法:點擊方法前的菱形(目前是對號/錯號)
- 執(zhí)行某個類的所有測試方法:點擊類前的菱形(目前是對號/錯號)
3正林、寫案例測試
我們在viewController里面添加一個方法計算工資稅泡一。如圖所示
我們想驗證這個方法是否計算正確,便可以創(chuàng)建測試來測試觅廓。如下圖所示
點每一個方法的左邊的菱形圖標就會單獨測試鼻忠。如果點類名旁邊的菱形圖標便會全類測試,執(zhí)行方法按順序執(zhí)行杈绸。
如上圖所示帖蔓,第二第三個方法驗證通過,第一個并沒有通過瞳脓。是我們通過XCTAssert
為我們提供的系統(tǒng)斷言 XSTAsserTrue
進行的判斷塑娇,那么除了 XSTAsserTrue
還有那些可以用于判斷的呢。下面簡單介紹常用的
4劫侧、常用斷言
- (void)testExample {
NSLog(@"--- 失敗之前的輸出");
XCTFail(@"無論怎么樣都是報錯");
NSLog(@"--- 失敗之后的輸出");
}
- (void)testNil {
// XCTAssertNil(expression, ...)
// expression為空時通過钝吮,否則測試失敗。
// expression接受id類型的參數板辽。
// XCTAssertNotNil(expression, ...)
// expression不為空時通過奇瘦,否則測試失敗。
NSString *name = @"小明";
// XCTAssertNil(name, @"報錯信息輸出");
// XCTAssertNotNil(name, @"viewcontroller 是 nil ");
}
- (void)testTrue {
// XCTAssert(expression, ...)
// expression為true時通過劲弦,否則測試失敗耳标。
// expression接受boolean類型的參數。
// XCTAssertTrue(expression, ...)
// expression為true時通過邑跪,否則測試失敗次坡。
// expression接受boolean類型的參數呼猪。
// XCTAssertFalse(expression, ...)
// expression為false時通過,否則測試失敗砸琅。
// expression接受boolean類型的參數宋距。
NSInteger number = 10;
// XCTAssert(number == 10, @"報錯信息輸出");
// XCTAssertTrue(number == 10, @"報錯信息輸出");
XCTAssertFalse(number != 10, @"報錯信息輸出");
}
- (void)testEqual {
// XCTAssertEqualObjects(expression1, expression2, ...)
// expression1和expression1地址相同時通過,否則測試失敗症脂。
// expression接受id類型的參數谚赎。
//
// XCTAssertNotEqualObjects(expression1, expression2, ...)
// expression1和expression1地址不相同時通過,否則測試失敗诱篷。
// expression接受id類型的參數壶唤。
//
// XCTAssertEqual(expression1, expression2, ...)
// expression1和expression1相等時通過,否則測試失敗棕所。
// expression接受基本類型的參數(數值闸盔、結構體之類的)。
//
// XCTAssertNotEqual(expression1, expression2, ...)
// expression1和expression1不相等時通過琳省,否則測試失敗迎吵。
// expression接受基本類型的參數。
// NSString *name = @"小明";
// NSString *name2 = name;
// NSString *name3 = @"小紅";
//
// XCTAssertEqualObjects(name, name2, @"name1 不等于 name 2");
// XCTAssertEqualObjects(name, @"小明", @"name1 不等于 小明");
// XCTAssertEqualObjects(name, name3, @"name1 不等于 name3");
NSInteger number1 = 10;
NSInteger number2 = 12;
// XCTAssertEqual(number1, number2, @"number1 不等于 number 2");
XCTAssertNotEqual(number1, number2, @"number1 等于 number 2");
}
5针贬、異步測試
XCTestExpectation 是系統(tǒng)為我們提供的異步測試的API击费,可以幫助我們測試接口,響應速度等坚踩。
如圖 我們創(chuàng)建分線程來計算工資稅
測試方法,如下圖
- 創(chuàng)建
XCTestExpectation
對象 - 設置等待時間
[self waitForExpectations:@[exp] timeout:0.5];
- 在計算完成時調用
fulfill
荡灾。
如果在規(guī)定時間內沒有調用就算超時會報錯瓤狐。
然后測試他執(zhí)行100萬次瞬铸,需要時間是否大于0.5秒。
顯然不可以础锐。(實測 1 秒多)
6嗓节、耦合測試
在現實項目中我們需要測試的案例肯定比以上所列舉內容要復雜。例如下舉例說明:如圖我們有一個計算 班級有多少人英語成績達到優(yōu)秀的算法皆警。
這個方法又是依賴與另一個工具類如下拦宣。
如果我們直接測試 優(yōu)秀英語學生數量的方法,是不行的信姓。因為我們創(chuàng)建的測試對象 他并沒有為 tool 初始化鸵隧。如圖所示:
那我們怎么解決這個問題呢。這個時候我們就需要 mock 和 stub 意推。簡單理解
- Mock 泛指模擬的類
- Stub 泛指模擬類的方法
那我們直接使用XCTest Mock 不就好了嗎豆瘫,結果是失望的,他并沒有為我們提供Mock功能菊值。 所以就需要我們去選擇一些適合的第三方外驱。
下文會舉案例介紹育灸,并提供demo。
4昵宇、單元測試框架選擇
1磅崭、行為驅動開發(fā)(BDD) 和 Kiwi框架 介紹
Kiwi 如 BDD所說做到了 通過用自然語言書寫非程序員可讀的測試用例
例: 如圖我們有一個Student類,并需要測試瓦哎。
使用kiwi測試代碼如下
內容很容易理解砸喻,就和在講故事一樣。
- 在所有開始之前我們需要一個stu對象
- 在所有之后我們需要銷毀測試對象
- 這個學生對象應該有名字
- 這個學生對象應該有所有科目有分數
- 他應該有總分杭煎,并且計算正確
這樣一個流程恩够,就是一個完成的測試流程。在測試過程中如果哪個環(huán)節(jié)出錯了羡铲,一目了然蜂桶。
2、測試驅動開發(fā)(TDD) XCTest+OCMock
測試驅動思想在第一章已經介紹過了也切,在這里就直接說案例了扑媚。
因為
XCTest
與Xcode
深度集成,這也是很多人選擇TDD
雷恃,避免第三方的集成疆股。在有需要的情況下在集成OCMock
。
在第三章第六節(jié)預留了一個問題倒槐。使用Mock和Stub解決測試 優(yōu)秀英語學生數量的方法的問題旬痹。如下圖當我們集成過OCMock之后
和之前對比,我們做的操作基本可以列成三部
- Mock一個
Student
對象 - Mock一個
StudentTool
對象讨越,并驗證 - 幫Mock的
StudentTool
置換給Student
的tool
并驗證
這樣我們便可以完成優(yōu)秀英語學生數量方法的測試两残。
由上我們可以看出一個問題,有依賴的方法是不方便與測試的把跨。所以說單元測試也是一種設計行為人弓,為了可以讓代碼得到優(yōu)質的測試環(huán)境,我們就會去寫一個優(yōu)質着逐,低耦合崔赌,邏輯清晰的代碼。這也是
TDD
的意義所在耸别。
3健芭、方案對比
由上文所說,似乎各有優(yōu)點秀姐,使用類似Kiwi
框架的BDD
一個是以講故事的形式來寫測試用例慈迈。使用XCTest + OCMock
的TDD
可以讓程序員寫出更好的代碼。那么他們是否有自的缺點呢囊扳。如下圖所示:
顯而易見吩翻,
TDD
更占優(yōu)勢兜看。在我們不集成第三方的情況XCTest
也能供我們編寫測試案例。并且當你嘗試去寫
Kiwi
的時候狭瞎,你會發(fā)現Kiwi
在未完全執(zhí)行所有測試用例時细移,是無法看到單個測試方法的,更無法執(zhí)行單個測試熊锭。Kiwi
的最小測試單位為一個測試用例類弧轧,而XCTest
的最小測試單位為測試用例類的一個測試方法。
那么既然XCTest+OCMock
這么好碗殷,別的第三方在寫測試用例的時候也都是這么選擇的嗎 精绎? 如圖
總結我認為 XCTest+OCMock是更好的選擇。
參考文獻:
更新中···
武漢加油锌妻,中國加油代乃。