- 0.緒論
- 1.什么是單元測(cè)試
- 2.為什么要做單元測(cè)試
-
3.iOS單元測(cè)試?案
- 測(cè)試框架:
- 測(cè)試對(duì)象:
- 測(cè)試工具:
-
1.XCTest
- 基本?法:
- UT三步曲:
- 注意事項(xiàng):
-
斷?:
- 真假斷言:Test a condition that generates a true or false result
- 等價(jià)斷言:Check whether two values are equal or not equal.
- 空/非空斷言:Check whether a test condition is nil or non-nil.
- 錯(cuò)誤斷言:Check whether a function call throws (or doesn’t throw) an error.
- 無(wú)條件失敗斷言:Generate a failure immediately and unconditionally.
- 異步操作測(cè)試:
- 代碼覆蓋率
- 2.OCMock
- 4.示例Demo
- 5.思考
- 參考文獻(xiàn)
0.緒論
基礎(chǔ)概念:BDD(Behavior Driven Development-行為驅(qū)動(dòng)開(kāi)發(fā))與TDD(Test Driven Development-測(cè)試驅(qū)動(dòng)開(kāi)發(fā))
TDD-測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
Test-driven development 測(cè)試驅(qū)動(dòng)開(kāi)發(fā)盾致,它是一種用一種測(cè)試先于編寫(xiě)代碼的思想來(lái)指導(dǎo)軟件開(kāi)發(fā)班缰。測(cè)試驅(qū)動(dòng)開(kāi)發(fā)是敏捷開(kāi)發(fā)中的一項(xiàng)核心實(shí)踐和技術(shù),其原理是在開(kāi)發(fā)功能代碼之前,先編寫(xiě)單元測(cè)試用例代碼俩垃,從而通過(guò)測(cè)試代碼確定需要編寫(xiě)什么具體的開(kāi)發(fā)代碼。使用這種做法的結(jié)果是一套全面的單元測(cè)試,可以隨時(shí)運(yùn)行以提供軟件可以正常工作的反饋。
BDD-行為驅(qū)動(dòng)開(kāi)發(fā)
Behavior-Driven Development透绩,行為驅(qū)動(dòng)開(kāi)發(fā)是指以軟件實(shí)際要達(dá)到的目標(biāo)也就是需求設(shè)計(jì)來(lái)驅(qū)動(dòng)整個(gè)開(kāi)發(fā)過(guò)程,更關(guān)注于用戶故事壁熄,即產(chǎn)品帚豪、開(kāi)發(fā)者、測(cè)試人員一起頭腦風(fēng)暴草丧,分析實(shí)際對(duì)應(yīng)用戶對(duì)于該軟件的需求狸臣,然后將這些需求寫(xiě)成一個(gè)個(gè)的故事。BDD重點(diǎn)是軟件開(kāi)發(fā)過(guò)程中使用的語(yǔ)言和交互昌执。開(kāi)發(fā)者負(fù)責(zé)填充故事的內(nèi)容滿足用戶需求烛亦,測(cè)試者負(fù)責(zé)檢驗(yàn)這些故事的結(jié)果是否是用戶所希望的成果诈泼。相比較TDD來(lái)說(shuō),這種開(kāi)發(fā)形式可以盡可能的避免用戶和開(kāi)發(fā)者在溝通上的障礙煤禽,實(shí)現(xiàn)客戶和開(kāi)發(fā)者同時(shí)定義系統(tǒng)的需求铐达,避免因?yàn)槔斫庑枨蟛怀浞侄鴰?lái)的不必要的工作量。
1.什么是單元測(cè)試
單元測(cè)試: Unit Testing檬果,顧名思義就是對(duì)最小的一個(gè)單元代碼或不受其他代碼邏輯影響的函數(shù)等進(jìn)行測(cè)試娶桦,對(duì)輸入的參數(shù)和輸出都要求比較明確。單元測(cè)試的目的是把程序里每個(gè)獨(dú)立的最小單元隔離開(kāi)來(lái)汁汗,保證其正確性,使得整個(gè)程序相對(duì)也更正確栗涂。(單元測(cè)試與接口測(cè)試的區(qū)別)
2.為什么要做單元測(cè)試
- 單元測(cè)試作為敏捷開(kāi)發(fā)實(shí)踐的組成之?知牌,?的是提?軟件開(kāi)發(fā)的效率,維持代碼的健壯性斤程,對(duì)我們的產(chǎn)品質(zhì)量是非常重要的
- 單元測(cè)試是所有測(cè)試中最底層的一類測(cè)試角寸,是第一個(gè)環(huán)節(jié),也是最重要的一個(gè)環(huán)節(jié)忿墅,是唯一一次有保證能夠代碼覆蓋率達(dá)到100%的測(cè)試扁藕,是整個(gè)軟件測(cè)試過(guò)程的基礎(chǔ)和前提,單元測(cè)試防止了開(kāi)發(fā)的后期因bug過(guò)多而失控疚脐,單元測(cè)試的性價(jià)比是最好的
- 據(jù)統(tǒng)計(jì)亿柑,大約有80%的錯(cuò)誤是在軟件設(shè)計(jì)階段引入的,并且修正一個(gè)軟件錯(cuò)誤所需的費(fèi)用將隨著軟件生命期的進(jìn)展而上升棍弄。錯(cuò)誤發(fā)現(xiàn)的越晚望薄,修復(fù)它的費(fèi)用就越高,而且呈指數(shù)增長(zhǎng)的趨勢(shì)
- 代碼規(guī)范呼畸、優(yōu)化痕支,可測(cè)試性的代碼
- 放心重構(gòu)
- 自動(dòng)化執(zhí)行three-thousand times
- 單元測(cè)試也有?些?級(jí)的作?,?如支持web?動(dòng)化測(cè)試蛮原、APP自動(dòng)化測(cè)試卧须、API接口自動(dòng)化測(cè)試等各種自動(dòng)化測(cè)試
3.iOS單元測(cè)試?案
測(cè)試框架:
Github上的一些知名的開(kāi)源庫(kù)使用的測(cè)試框架:
常用的測(cè)試框架:
- XCTest:Apple自帶,與Xcode深度集成且享受Apple后續(xù)對(duì)XCTest升級(jí)維護(hù)儒陨。
- KiWi:第三方插件花嘶,面向BDD思想
AFNetworking是一個(gè)輕量級(jí)的iOS網(wǎng)絡(luò)通信類庫(kù)。它建立在NSURLConnection和NSOperation等類庫(kù)的基礎(chǔ)上框全,讓很多網(wǎng)絡(luò)通信功能的實(shí)現(xiàn)變得十分簡(jiǎn)單察绷,同時(shí)它支持HTTP請(qǐng)求和基于REST的網(wǎng)絡(luò)服務(wù)(包括GET、POST津辩、 PUT拆撼、DELETE等)容劳,且支持ARC。
采用?案:XCTest + OCMock (mock對(duì)象闸度、樁程序)
測(cè)試對(duì)象:
?絡(luò)請(qǐng)求接?
?具類?法
部分可測(cè)試視圖邏輯
私有?法
測(cè)試工具:
1.XCTest
XCTest是Xcode集成的?套單元測(cè)試框架竭贩,以下是?些基本使? :
基本?法:
- (void)setUp {
[super setUp];
// 每個(gè)test?法執(zhí)?前調(diào)?,在這個(gè)測(cè)試?例?進(jìn)??些通?的初始化?作
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
[super tearDown];
// 每個(gè)test?法執(zhí)?后調(diào)?
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testExample {
// 測(cè)試?法樣例 // This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
- (void)testPerformanceExample {
//這個(gè)?法主要是做性能測(cè)試的莺禁,所謂性能測(cè)試留量,主要就是評(píng)估?段代碼的運(yùn)?時(shí)間。該?法就是性能測(cè)試?法的樣例哟冬。
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
UT三步曲:
- 準(zhǔn)備數(shù)據(jù)
- 調(diào)用方法
- 斷言結(jié)果, 判斷查看結(jié)果
示例代碼:
//書(shū)寫(xiě)規(guī)范:方法名必須 以 “test” 開(kāi)頭
- (void)testStatus_fLevel {
//1.數(shù)據(jù)準(zhǔn)備
BMKMapStatus *status = [[BMKMapStatus alloc] init];
status.fLevel = 12.0;
//2.調(diào)用方法
[_mapView setMapStatus:status];
BMKMapStatus *rstatus = [_mapView getMapStatus];
//3.斷言結(jié)果, 判斷查看結(jié)果
XCTAssertEqualWithAccuracy(rstatus.fLevel, 12.0, 0.1, "flevel wrong");
}
注:對(duì)于該用例楼熄,代碼執(zhí)行順序:setUp--->testStatus_fLevel--->tearDown
注意事項(xiàng):
- 1.整個(gè)工程中應(yīng)該有多個(gè)XCTest文件,每一個(gè)XCTests都是只有.m文件的浩峡,繼承自XCTestCase(當(dāng)然如果你有公共配置可岂,其實(shí)可以先新建繼承自XCTestCase類的一個(gè)Cocoa Touch Class類(如UserDefinedBaseTestCase)當(dāng)作自己的XCTest所有類的父類,然后對(duì)于測(cè)試的基本配置/公用方法都可以在這個(gè)父類中實(shí)現(xiàn)翰灾;其他子類文件可以直接繼承自UserDefinedBaseTestCase類就可以直接調(diào)用公有方法了)
- 2.每個(gè)XCTests.m文件中缕粹,必然包含一個(gè)setUp方法和一個(gè)tearDown方法
- 3.每個(gè)單元測(cè)試方法執(zhí)行之前,XCTest會(huì)先執(zhí)行setUp方法纸淮,所以我們可以在這個(gè)方法中進(jìn)行初始化參數(shù)或測(cè)試環(huán)境的配置
- 4.每個(gè)單元測(cè)試方法執(zhí)行結(jié)束后平斩,XCTest會(huì)執(zhí)行tearDown方法,所以可以把需要測(cè)試完成后銷毀的內(nèi)容邏輯放在這里咽块,以便保證下面的測(cè)試不受本次測(cè)試影響
- 5.每個(gè)單元測(cè)試方法都應(yīng)該以test開(kāi)頭
斷?:
XCTAssert(expression, ...) XCTAssert(條件, 不滿?條件的描述)
eg.
XCTAssert(a == 0, @“a不能等于0”)
真假斷言:Test a condition that generates a true or false result
XCTAssertTrue(expression, ...) 當(dāng)expression == false時(shí)報(bào)錯(cuò)
XCTAssertFalse(expression, ...) 當(dāng)expression != false時(shí)報(bào)錯(cuò)
等價(jià)斷言:Check whether two values are equal or not equal.
XCTAssertEqualObjects(expression1, expression2, format...) 當(dāng)expression1不等于expression2時(shí)報(bào)錯(cuò)
XCTAssertNotEqualObjects(expression1, expression2, format...) 當(dāng)expression1 等于expression2時(shí)報(bào)錯(cuò)
XCTAssertEqual(expression1, expression2, format...) 當(dāng)expression1不等于 expression2時(shí)報(bào)錯(cuò)绘面,這個(gè)斷言?于C語(yǔ)?的標(biāo)量
XCTAssertNotEqual(expression1, expression2, format...) 當(dāng)expression1等于 expression2時(shí)報(bào)錯(cuò),這個(gè)斷言?于C語(yǔ)?的標(biāo)量
XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, format...) 當(dāng) expression1和expression2之間的差別?于accuracy將報(bào)錯(cuò)糜芳。這種斷言適?于 floats和doubles這些標(biāo)量(C scalar type)飒货,兩者之間的細(xì)微差異導(dǎo)致它們不完全相等,但是對(duì)所有的標(biāo)量都有效
XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, format...) 當(dāng)expression1和expression2之間的差別低于accuracy將報(bào)錯(cuò)峭竣。這種斷言適?于floats和doubles這些標(biāo)量塘辅,兩者之間的細(xì)微差異導(dǎo)致它們不完全相等,但是對(duì)所有的標(biāo)量都有效
空/非空斷言:Check whether a test condition is nil
or non-nil
.
XCTAssertNil(expression, format...) 當(dāng)expression參數(shù)?nil時(shí)報(bào)錯(cuò)
XCTAssertNotNil(expression, format...) 當(dāng)expression參數(shù)為nil時(shí)報(bào)錯(cuò)
錯(cuò)誤斷言:Check whether a function call throws (or doesn’t throw) an error.
XCTAssertThrows(expression, format...) 當(dāng)expression不拋出異常時(shí)報(bào)錯(cuò)
XCTAssertThrowsSpecific(expression, exception_class, format...) 當(dāng)expression 針對(duì)指定類不拋出異常時(shí)報(bào)錯(cuò)
XCTAssertThrowsSpecificNamed(expression, exception_class, exception_name, format...) 當(dāng)expression針對(duì)特定類和特定名字不拋出異常時(shí)報(bào)錯(cuò)皆撩。對(duì)于AppKit框 架或Foundation框架?常有?扣墩,拋出帶有特定名字的NSException(NSInvalidArgumentException等等)
XCTAssertNoThrow(expression, format...) 當(dāng)expression拋出異常時(shí)報(bào)錯(cuò)
XCTAssertNoThrowSpecific(expression, exception_class, format...) 當(dāng) expression針對(duì)指定類拋出異常時(shí)報(bào)錯(cuò)。任意其他異常都可以扛吞;也就是說(shuō)它不會(huì)報(bào) 錯(cuò)
XCTAssertNoThrowSpecificNamed(expression, exception_class, exception_name, format...) 當(dāng)expression針對(duì)特定類和特定名字拋出異常時(shí)報(bào)錯(cuò)呻惕。
對(duì)于AppKit框架或Foundation框架?常有?,拋出帶有特定名字的 NSException(NSInvalidArgumentException等
無(wú)條件失敗斷言:Generate a failure immediately and unconditionally.
XCTFail(...) 無(wú)條件返回失敗
異步操作測(cè)試:
有些需要測(cè)試的方法可能會(huì)執(zhí)行異步網(wǎng)絡(luò)操作滥比,也許并不能立即返回服務(wù)器響應(yīng)的結(jié)果亚脆,這就需要我們考慮實(shí)現(xiàn)異步操作的單元測(cè)試,而XCTestCase內(nèi)置支持測(cè)試異步方法
- (XCTestExpectation *)expectationWithDescription:(NSString *)description:方法獲取XCTestExpectation的實(shí)例盲泛,在這一模式下濒持,測(cè)試完成的方法并不會(huì)被標(biāo)記為測(cè)試用例通過(guò)键耕;
- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout handler:(nullable XCWaitCompletionHandler)handler:方法用來(lái)等待操作完成,如果測(cè)試用例沒(méi)有完成柑营,那么將會(huì)調(diào)用回調(diào)處理的塊
- (void)fulfill:XCTestExpectation為fulfilled狀態(tài)的話代表異步操作已經(jīng)完成屈雄,可以檢測(cè)是否符合預(yù)期
代碼覆蓋率
在自動(dòng)化單元測(cè)試或功能測(cè)試中,經(jīng)常使用代碼覆蓋率用來(lái)表示經(jīng)過(guò)測(cè)試的代碼的百分比官套,以保證我們的代碼能夠經(jīng)過(guò)足夠的測(cè)試酒奶。
我們可以開(kāi)通xcode的代碼覆蓋收集,開(kāi)通后就可以在導(dǎo)航欄的報(bào)告導(dǎo)航欄中看到測(cè)試記錄及結(jié)果奶赔,還可以選擇點(diǎn)擊??跳轉(zhuǎn)到測(cè)試代碼處惋嚎,還可以點(diǎn)擊Coverage或者Logs標(biāo)簽查看覆蓋代碼和測(cè)試日志
2.OCMock
我們要測(cè)試的?法會(huì)引?很多外部依賴的對(duì)象,?我們沒(méi)法控制這些外部依賴的對(duì)象站刑。為了解決這個(gè)問(wèn)題瘸彤,我們需要?到Stub和Mock來(lái)模擬這些外部依賴的對(duì)象,從?控制它們。單獨(dú)依靠XCTest難以完成Mock或者Stub但是結(jié)合OCMock可以在測(cè)試代碼中實(shí)現(xiàn)這以下功能:
● Stub --- 樁程序, ?為地讓?個(gè)對(duì)象對(duì)某個(gè)?法返回我們事先規(guī)定好的值
eg.
OCMStub([mockCar getCarBrand:[OCMArg any]]).andReturn(@"XXX");
[OCMArg any]表示可以為任意參數(shù)笛钝,若改為具體參數(shù)(如:張三),表示只有參數(shù) 為張三時(shí)才會(huì)觸發(fā)stup愕宋,否則不會(huì)觸發(fā)
● Mock --- 模擬對(duì)象, ?個(gè)對(duì)象, 它是對(duì)現(xiàn)有類的?為?種模擬(或是對(duì)現(xiàn)有接?實(shí)現(xiàn)的模擬), 它只能響應(yīng)那些你添加了期望或者 stub 的?法
eg.
// 1.nice mock - 不會(huì)在?個(gè)沒(méi)有被stub的?法被調(diào)?時(shí)拋出異常
Car *mockCar = OCMClassMock([Car class]);
// 2.vanilla mock - 在mock的?命周期中每?個(gè)?法調(diào)?都必須是stub過(guò)的?法玻靡。當(dāng)調(diào)??個(gè)沒(méi)有stub的?法的時(shí)候會(huì)拋出?個(gè)異常
Car *mockCar = OCMStrictClassMock([Car class]);
// 3.partial mock - 當(dāng)?個(gè)沒(méi)有stub過(guò)的?法被調(diào)?了,這個(gè)?法會(huì)被轉(zhuǎn)發(fā)到真 實(shí)的對(duì)象上中贝。
Car *mockCar = OCMPartialMock([Car class]);
4.示例Demo
4.1 測(cè)試步驟
- 給定待測(cè)的接口囤捻、代碼模塊以及相應(yīng)API文檔
- 熟悉接口、理清內(nèi)部邏輯邻寿、分支結(jié)構(gòu)等細(xì)節(jié)
- 設(shè)計(jì)用例蝎土、編寫(xiě)單元測(cè)試
- 執(zhí)行,斷言绣否,觀察結(jié)果和預(yù)期
4.2 案例實(shí)現(xiàn)
- 對(duì)于給定接口(網(wǎng)絡(luò)請(qǐng)求類接口)誊涯,通過(guò)接口注釋文檔明確其接口功能,輸入?yún)?shù)蒜撮、輸出結(jié)果暴构、請(qǐng)求方式等信息:
-接口功能:公交路線檢索(僅支持市內(nèi))
-輸入?yún)?shù):BMKTransitRoutePlanOption對(duì)象
-返回結(jié)果:BOOL 值,YES/NO
-請(qǐng)求類型:異步請(qǐng)求(即請(qǐng)求不在主線程操作)
/**
*公交路線檢索(僅支持市內(nèi))
*異步函數(shù)段磨,返回結(jié)果在BMKRouteSearchDelegate的onGetTransitRouteResult通知
*@param transitRoutePlanOption 公交換乘信息類
*@return 成功返回YES取逾,否則返回NO
*/
- (BOOL)transitSearch:(BMKTransitRoutePlanOption *)transitRoutePlanOption;
- 通過(guò)代碼走讀(查看接口具體實(shí)現(xiàn)),熟悉接口苹支、理清內(nèi)部邏輯砾隅、分支結(jié)構(gòu)等細(xì)節(jié):
-入?yún)⒌暮戏ㄐ孕r?yàn)
-代碼分支結(jié)構(gòu)是否合理
-代碼魯棒性(健壯性、可靠性)
此處省略一萬(wàn)字
- 設(shè)計(jì)用例债蜜、編寫(xiě)單元測(cè)試
-保證每個(gè)單元測(cè)試用例覆蓋最小粒度的代碼邏輯
-全部單元測(cè)試用例疊加起來(lái)盡最大可能覆蓋待測(cè)接口(模塊)全部代碼行晴埂、條件分支
#import <XCTest/XCTest.h>
#import <xxx_Search/BMKSearchComponent.h>
@interface RoutePlanTransitSearchTest : XCTestCase<BMKRouteSearchDelegate>
{
BMKRouteSearch *routeSearch;
XCTestExpectation *expectation;
int tag;
}
@end
@implementation RoutePlanTransitSearchTest
- (void)setUp {
[super setUp];
routeSearch = [[BMKRouteSearch alloc]init];
routeSearch.delegate = self;
}
- (void)tearDown {
routeSearch.delegate = nil;
routeSearch = nil;
expectation = nil;
[super tearDown];
}
/**
* "定義一個(gè)TransitRoutePlanOption對(duì)象
* 1究反、option為null
* 驗(yàn)證:返回NO
*/
- (void)testTransitRoutePlanSearch_Option_Null{
Boolean flag = [routeSearch transitSearch:nil];
XCTAssertFalse(flag, "option null return NO");
}
/**
* "定義一個(gè)TransitRoutePlanOption對(duì)象
* 1、from節(jié)點(diǎn)為null
* 2邑时、to節(jié)點(diǎn)正常"
* 3奴紧、city正常
* 驗(yàn)證:返回NO
*/
- (void)testTransitRoutePlanSearch_From_Null{
BMKPlanNode* end = [[BMKPlanNode alloc]init];
end.cityName = @"北京";
end.name = @"百度大廈";
BMKTransitRoutePlanOption *option = [[BMKTransitRoutePlanOption alloc] init];
option.city = @"北京市";
option.from = nil;
option.to = end;
Boolean flag = [routeSearch transitSearch:option];
XCTAssertFalse(flag, "from null return NO");
}
/**
* "定義一個(gè)TransitRoutePlanOption對(duì)象
* 1、from節(jié)點(diǎn)正常
* 2晶丘、to節(jié)點(diǎn)null
* 3黍氮、city正常
* 驗(yàn)證:返回NO
*/
- (void)testTransitRoutePlanSearch_To_Null{
BMKPlanNode* start = [[BMKPlanNode alloc]init];
start.cityName = @"北京";
start.name = @"奎科科技大廈";
BMKTransitRoutePlanOption *option = [[BMKTransitRoutePlanOption alloc] init];
option.city = @"北京市";
option.from = start;
option.to = nil;
Boolean flag = [routeSearch transitSearch:option];
XCTAssertFalse(flag, "to null return NO");
}
/**
* "定義一個(gè)BMKTransitRoutePlanOption對(duì)象
* 1、from節(jié)點(diǎn)正常
* 2浅浮、to節(jié)點(diǎn)正常
* 3沫浆、city為null,即未指定在哪個(gè)城市檢索
* 驗(yàn)證:端上攔截滚秩,請(qǐng)求失敗专执,返回NO
*/
- (void)testTransitRoutePlanSearch_City_Null{
BMKPlanNode* start = [[BMKPlanNode alloc]init];
BMKPlanNode* end = [[BMKPlanNode alloc]init];
start.cityName = @"北京";
start.name = @"奎科科技大廈";
end.cityName = @"北京";
end.name = @"百度大廈";
BMKTransitRoutePlanOption *option = [[BMKTransitRoutePlanOption alloc] init];
option.city = nil;
option.from = start;
option.to = end;
Boolean flag = [routeSearch transitSearch:option];//預(yù)期:端上攔截,請(qǐng)求失敗郁油,返回NO
XCTAssertFalse(flag, @"起終點(diǎn)使用關(guān)鍵字時(shí)本股,必須設(shè)定城市,不能為空或者空字符串");
NSLog(@"testTransitRoutePlanSearch_City_Null flag %d", flag);
}
//city.length > MAX_CITY_LENGTH, 返回NO
- (void)testTransitRoutePlanSearch_Max_City{
BMKPlanNode* start = [[BMKPlanNode alloc]init];
BMKPlanNode* end = [[BMKPlanNode alloc]init];
start.cityName = @"北京";
start.name = @"奎科科技大廈";
end.cityName = @"北京";
end.name = @"天通苑北一區(qū)";
BMKTransitRoutePlanOption *option = [[BMKTransitRoutePlanOption alloc] init];
option.city = @"北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京北京";
option.from = start;
option.to = end;
Boolean flag = [routeSearch transitSearch:option];
XCTAssertFalse(flag, @"city lenght > max return false");
}
/**
* "定義一個(gè)TransitRoutePlanOption對(duì)象
* 1桐腌、from的PlanNode節(jié)點(diǎn)拄显,如北京,奎科科技大廈
* 2案站、to的PlanNode節(jié)點(diǎn)躬审,如北京,天通苑北一區(qū)"
* 驗(yàn)證:
* "返回的TransitRouteResult不為空蟆盐,errorcode為no_error
* 1承边、getRouteLines()不為空
* 2、每條TransitRouteLine中g(shù)etWayPoints()不為空,getAllStep()不為空
* 3石挂、每個(gè)路段節(jié)點(diǎn)博助,getEntrance()、getStepType()痹愚、getExit()翔始、getInstructions()、getVehicleInfo()里伯、不為空
* 4城瞎、當(dāng)路段公交或地鐵,VehicleInfo中g(shù)etPassStationNum()疾瓮、getTitle()脖镀、getTotalPrice()、getUid()、getZonePrice()不為空"
*/
- (void)testTransitRoutePlanSearch_Normal{
tag = 1;
expectation = [self expectationWithDescription:@"search result"];
BMKPlanNode* start = [[BMKPlanNode alloc]init];
BMKPlanNode* end = [[BMKPlanNode alloc]init];
start.cityName = @"北京";
start.name = @"奎科科技大廈";
end.cityName = @"北京";
end.name = @"天通苑北一區(qū)";
BMKTransitRoutePlanOption *option = [[BMKTransitRoutePlanOption alloc] init];
option.city = @"北京市";
option.from = start;
option.to = end;
Boolean flag = [routeSearch transitSearch:option];
XCTAssertTrue(flag, @"normal search return ture");
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testTransitRoutePlanSearch_Normal_Pt{
tag = 1;
expectation = [self expectationWithDescription:@"search result"];
BMKPlanNode* start = [[BMKPlanNode alloc]init];
BMKPlanNode* end = [[BMKPlanNode alloc]init];
start.pt = CLLocationCoordinate2DMake(40.0477880000,116.3132600000);
end.pt = CLLocationCoordinate2DMake(40.0830240000,116.4239230000);
BMKTransitRoutePlanOption *option = [[BMKTransitRoutePlanOption alloc] init];
option.city = @"北京市";
option.from = start;
option.to = end;
Boolean flag = [routeSearch transitSearch:option];
XCTAssertTrue(flag, @"normal search return ture");
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
/**
* "定義一個(gè)TransitRoutePlanOption對(duì)象
* 1蜒灰、from的PlanNode節(jié)點(diǎn)弦蹂,通過(guò)withCityCodeAndPlaceName設(shè)置,如131强窖,奎科科技大廈
* 2凸椿、to的PlanNode節(jié)點(diǎn),通過(guò)withCityNameAndPlaceName設(shè)置翅溺,如北京脑漫,天通苑北一區(qū)
* 3、policy為EBUS_NO_SUBWAY"
*
* "返回的TransitRouteResult不為空咙崎,errorcode為no_error
* 1优幸、getRouteLines()不為空
* 2、每條TransitRouteLine中g(shù)etWayPoints()不為空褪猛,getAllStep()不為空
* 3网杆、每個(gè)路段節(jié)點(diǎn),getEntrance()伊滋、getStepType()不為SUBWAY,getExit()碳却、getInstructions()、getVehicleInfo()笑旺、不為空
* 4追城、當(dāng)路段公交或地鐵,VehicleInfo中g(shù)etPassStationNum()燥撞、getTitle()、getTotalPrice()迷帜、getUid()物舒、getZonePrice()不為空"
*/
- (void)testTransitRoutePlanSearch_Normal_With_Policy{
tag = 1;
expectation = [self expectationWithDescription:@"search result"];
BMKPlanNode* start = [[BMKPlanNode alloc]init];
BMKPlanNode* end = [[BMKPlanNode alloc]init];
start.cityName = @"北京";
start.name = @"奎科科技大廈";
end.cityName = @"北京";
end.name = @"天通苑北一區(qū)";
BMKTransitRoutePlanOption *option = [[BMKTransitRoutePlanOption alloc] init];
option.city = @"北京市";
option.from = start;
option.to = end;
option.transitPolicy = BMK_TRANSIT_NO_SUBWAY;
Boolean flag = [routeSearch transitSearch:option];
XCTAssertTrue(flag, @"normal search return ture");
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
/**
* "定義一個(gè)TransitRoutePlanOption對(duì)象
* 1、from的PlanNode節(jié)點(diǎn)戏锹,通過(guò)withCityCodeAndPlaceName設(shè)置冠胯,如131,”窩窩窩“
* 2锦针、to的PlanNode節(jié)點(diǎn)荠察,通過(guò)withCityNameAndPlaceName設(shè)置,如北京奈搜,”哈哈哈哈“
* 返回的TransitRouteResult不為空悉盆,errorcode為result_not_found
*/
- (void)testTransitRoutePlanSearch_Result_Not_Found{
tag = 2;
expectation = [self expectationWithDescription:@"search result"];
BMKPlanNode* start = [[BMKPlanNode alloc]init];
BMKPlanNode* end = [[BMKPlanNode alloc]init];
start.cityName = @"北京";
start.name = @"gute street";
end.cityName = @"北京";
end.name = @"gute platz";
BMKTransitRoutePlanOption *option = [[BMKTransitRoutePlanOption alloc] init];
option.city = @"北京市";
option.from = start;
option.to = end;
option.transitPolicy = BMK_TRANSIT_NO_SUBWAY;
Boolean flag = [routeSearch transitSearch:option];
XCTAssertTrue(flag, @"normal search return true");
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testTransitRoutePlanSearch_Result_Ambiguous{
tag = 3;
expectation = [self expectationWithDescription:@"search result"];
BMKPlanNode* start = [[BMKPlanNode alloc]init];
BMKPlanNode* end = [[BMKPlanNode alloc]init];
start.cityName = @"北京";
start.name = @"百度大廈";
end.cityName = @"北京";
end.name = @"小明";
BMKTransitRoutePlanOption *option = [[BMKTransitRoutePlanOption alloc] init];
option.city = @"北京市";
option.from = start;
option.to = end;
option.transitPolicy = BMK_TRANSIT_NO_SUBWAY;
Boolean flag = [routeSearch transitSearch:option];
XCTAssertTrue(flag, @"normal search return true");
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)onGetTransitRouteResult:(BMKRouteSearch*)searcher result:(BMKTransitRouteResult*)result errorCode:(BMKSearchErrorCode)error{
switch (tag) {
case 1:
XCTAssertEqual(error, BMK_SEARCH_NO_ERROR, @"error code wrong");
XCTAssertNotNil(result, @"result nil");
XCTAssertNotNil(result.routes, @"routes nil");
for (BMKTransitRouteLine* plan in result.routes) {
XCTAssertTrue(plan.distance > 0, @"plan distance 0");
XCTAssertNotNil(plan.duration, @"plan duration nil");
XCTAssertNotNil(plan.starting, @"plan start nil");
XCTAssertNotNil(plan.terminal, @"plan end nil");
XCTAssertNotNil(plan.steps, @"plan steps nil");
}
break;
case 2:
XCTAssertEqual(error, BMK_SEARCH_RESULT_NOT_FOUND, @"error code wrong");
XCTAssertNil(result, @"result nil");
break;
case 3:
XCTAssertEqual(error, BMK_SEARCH_AMBIGUOUS_ROURE_ADDR, @"error code wrong");
XCTAssertNotNil(result, @"result nil");
XCTAssertNotNil(result.suggestAddrResult, @"suggestAddrResult nil");
XCTAssertNil(result.routes, @"routes nil");
break;
default:
break;
}
[expectation fulfill];
}
@end
-
執(zhí)行,斷言馋吗,觀察結(jié)果和預(yù)期
image.png
5.思考
單元測(cè)試的覆蓋率越全越好焕盟,當(dāng)覆蓋率(分支覆蓋率、行覆蓋率)達(dá)到百分百時(shí)就能說(shuō)明待測(cè)接口或模塊沒(méi)有問(wèn)題了宏粤?
是否考慮代碼分支邏輯的正確性單元測(cè)試脚翘,除了關(guān)注待測(cè)對(duì)象的功能正確性之外灼卢,是否還需要關(guān)注其他方面?
用戶體驗(yàn):錯(cuò)誤碼類型来农、日志提示的合理性|可讀性對(duì)于網(wǎng)絡(luò)請(qǐng)求類接口鞋真,如何保證case運(yùn)行的穩(wěn)定性?
考慮網(wǎng)絡(luò)延遲沃于,延遲操作涩咖,等待返回結(jié)果,具體方法:
(1)在主線程中適當(dāng)sleep揽涮,等待返回結(jié)果
(2)在單測(cè)內(nèi)部延遲判斷
_expectation = [self expectationWithDescription:@"search result"];
BMKReverseGeoCodeSearchOption *rgcOption = [[BMKReverseGeoCodeSearchOption alloc] init];
rgcOption.location = CLLocationCoordinate2DMake(40.049850, 116.279920);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
BOOL result = [_geoSearch reverseGeoCode:rgcOption];
XCTAssertTrue(result, @"geoSearch should return true");
});
[self waitForExpectationsWithTimeout:5.0 handler:nil];