原創(chuàng)blog,轉(zhuǎn)載請(qǐng)注明出處?
歡迎關(guān)注我的iOS-SDK詳解專欄?
http://blog.csdn.net/column/details/huangwenchen-ios-sdk.html
前言:測(cè)試是一個(gè)好的App不可缺少的部分。每一個(gè)App都是由一個(gè)個(gè)小的功能組合到一起的。而這些小的功能又是由一個(gè)個(gè)函數(shù)或者說算法組合到一起的。單元測(cè)試就是對(duì)這些小的功能或者函數(shù)進(jìn)行測(cè)試,良好的單元測(cè)試會(huì)讓代碼的健壯性提高很多。XCTest就是XCode為我們提供的一個(gè)框架,它提供了各個(gè)層次的測(cè)試。
每個(gè)XCode創(chuàng)建iOS的工程中都有一個(gè)叫做”工程名Tests”的分組,這個(gè)分組里就是XCTestCase的子類,XCTest中的測(cè)試類都是繼承自XCTestCase。?
例如新建一個(gè)工程,命名為Demo,就能看到如圖?
看一下這個(gè)自動(dòng)創(chuàng)建的文件里都包含了哪些內(nèi)容
#import ?
@interface?DemoTests : XCTestCase?
@end
?@implementation?DemoTests
- (void)setUp {
????[super?setUp];?
????//?Put setup code here. This method?iscalled before the invocation?of?each test method?in?the?class.
?}
- (void)tearDown {?
????//?Put teardown code here. This method?is?called after the invocation?of?each test method?in?the?class.?
????[super?tearDown];
}
- (void)testExample {?
????//?This?is?an example?of?a functional test?case.
????XCTAssert(YES, @"Pass");
}
- (void)testPerformanceExample {?
????//?This?is?an example?of?a performance test?case. [self?measureBlock:^{?
????//?Put the code you want to measure the time?of?here. }];
}
?@end
XCTest中所有的測(cè)試用例的命名都是以test開頭的。例如上文中的
- (void)testExample {
//?This?is?an example?of?a functional test?case.
????XCTAssert(YES, @"Pass");
}
Setup是在所有測(cè)試用例運(yùn)行之前運(yùn)行的函數(shù),在這個(gè)測(cè)試用例里進(jìn)行一些通用的初始化工作
tearDown是在所有的測(cè)試用例都執(zhí)行完畢后執(zhí)行的
測(cè)試用例的導(dǎo)航如圖,在測(cè)試用例的導(dǎo)航里,我們可以運(yùn)行一組測(cè)試用例,也可以運(yùn)行一個(gè)單獨(dú)的測(cè)試用例?
可以鼠標(biāo)右鍵來新建一組測(cè)試用例。?
也可以為測(cè)試用例添加失敗斷點(diǎn)來方便我們調(diào)試?
通過測(cè)試導(dǎo)航欄可以查看到測(cè)試結(jié)果
通過Report導(dǎo)航欄可以看到更詳細(xì)的測(cè)試結(jié)果
點(diǎn)擊測(cè)試用例后面的箭頭,可以跳轉(zhuǎn)到測(cè)試用例的代碼。
例如,新建一個(gè)類命名為Model,他有這個(gè)方法用來生成10以內(nèi)的隨機(jī)數(shù)。
- (NSInteger)randomLessThanTen{
????return arc4random()%10;
}
于是,測(cè)試方法為
-(void)testModelFunc_randomLessThanTen{
????Model * model = [[Model alloc] init];
????NSInteger num = [model randomLessThanTen];
????XCTAssert(num<10,@"num should less than 10");
}
我們點(diǎn)擊如圖的左邊圖標(biāo)單獨(dú)運(yùn)行這個(gè)測(cè)試用例,當(dāng)然也可以在上文我提到的導(dǎo)航欄里單獨(dú)運(yùn)行。?
然后會(huì)看到輸出表示這個(gè)測(cè)試用例通過
Test Suite?'Selected tests'?started?at?2015-06-28?05:24:56?+0000?
Test Suite?'DemoTests.xctest'?started?at?2015-06-28?05:24:56?+0000?
Test Suite?'DemoTests'?started?at?2015-06-28?05:24:56?+0000?
Test Case?'-[DemoTests testModelFunc_randomLessThanTen]'?started.
Test Case?'-[DemoTests testModelFunc_randomLessThanTen]'?passed (0.000?seconds).
Test Suite?'DemoTests'?passed?at?2015-06-28?05:24:56?+0000.?Executed?1?test,?with?0?failures (0?unexpected)?in?0.000?(0.001)?seconds?
Test Suite?'DemoTests.xctest'?passed?at?2015-06-28?05:24:56?+0000.?Executed?1?test,?with?0?failures (0?unexpected)?in?0.000?(0.001)?seconds?
Test Suite?'Selected tests'?passed?at?2015-06-28?05:24:56?+0000.
如何判斷一個(gè)測(cè)試用例成功或者失敗呢?XCTest使用斷言來實(shí)現(xiàn)。?
最基本的斷言?
表示如果expression滿足,則測(cè)試通過,否則對(duì)應(yīng)format的錯(cuò)誤。
XCTAssert(expression,?format...)
還有一個(gè)用來直接Fail的斷言
XCTFail(format...)
其他一些常用的斷言:
XCTAssertTrue(expression,?format...)
XCTAssertFalse(expression,?format...)
XCTAssertEqual(expression1, expression2,?format...)
XCTAssertNotEqual(expression1, expression2,?format...)
XCTAssertEqualWithAccuracy(expression1, expression2, accuracy,?format...)
XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy,?format...)
XCTAssertNil(expression,?format...) XCTAssertNotNil(expression,?format...)
所謂性能測(cè)試,主要就是評(píng)估一段代碼的運(yùn)行時(shí)間,XCTest的性能的測(cè)試?yán)萌缦赂袷?/p>
對(duì)于性能測(cè)試,每一個(gè)測(cè)試用例每次會(huì)運(yùn)行10次。
- (void)testPerformanceExample {?
????/?This?is?an example?of?a performance test?case.
????[self?measureBlock:^{?
????/?Put the code you want to measure the time?of?here.
????}];
}
例如,我要評(píng)估一段代碼,循環(huán)打印NSLog 10000次。?
這段代碼如下,這段代碼我放在UIImage的類別里。
- (void)testPerformanceExample {?
????// This is an example of a performance test case.
?????[self measureBlock:^{?
????????for?(NSInteger?index?=?0;?index?<?10000;?index?++) {
????????????NSLog(@"%ld",index);
????????}?
????????// Put the code you want to measure the time of here.?
????}];
}
我們都知道,測(cè)試要么成功,要么失敗,那么就引入了一個(gè)關(guān)鍵的問題
性能測(cè)試的時(shí)候,如何判一個(gè)性能測(cè)試case是成功還是失敗呢?
我們先通過上文的方式,只運(yùn)行一次這個(gè)測(cè)試用例。然后看看結(jié)果和輸出(這個(gè)測(cè)試用例跑的很慢,別著急)
Test Case?'-[ModelTests testPerformanceExample]'?failed (37.432?seconds).
Test Suite?'ModelTests'?failed?at?2017-02-19?09:57:26.210. Executed?1?test,?with?1?failure (0?unexpected)?in?37.432?(37.433)?seconds
?Test Suite?'ToDoTests.xctest'?failed?at?2017-02-19?09:57:26.211. Executed?1?test,?with?1?failure (0?unexpected)?in?37.432?(37.434)?seconds
?Test Suite?'Selected tests'?failed?at?2017-02-19?09:57:26.211. Executed?1?test,?with?1?failure (0?unexpected)?in?37.432?(37.437)?seconds?
Test session?log: /Users/hl/Library/Developer/Xcode/DerivedData/ToDo-bbcdkwvzbmyznocgystdcavfakca/Logs/Test/98E0FA82-BACC-4361-AF39-E0734F73A545/Session-ToDoTests-2017-02-19_095641-jm2eKF.log
然后,你會(huì)發(fā)現(xiàn)測(cè)試失敗了!這是因?yàn)槲覀儧]有給性能測(cè)試一個(gè)參考時(shí)間。?
我們點(diǎn)擊圖中的的第二個(gè)叉箭頭
然后,看到如圖?
我們來看看這幾個(gè)參數(shù)都是啥意思:
Baseline 計(jì)算標(biāo)準(zhǔn)差的參考值
MAX STDD 最大允許的標(biāo)準(zhǔn)差
底部點(diǎn)擊1,2…10可以看到每次運(yùn)行的結(jié)果。
點(diǎn)擊Edit,我們點(diǎn)擊Average的右邊的Accept,來讓本次運(yùn)行的平均值設(shè)置為baseline,然后然后MAX STDD改為40%。再運(yùn)行這個(gè)測(cè)試用例,你會(huì)發(fā)現(xiàn)測(cè)試成功了。
異步測(cè)試的邏輯如下,首先定義一個(gè)或者多個(gè)XCTestExpectation,表示異步測(cè)試想要的結(jié)果。然后設(shè)置timeout,表示異步測(cè)試最多可以執(zhí)行的時(shí)間。最后,在異步的代碼完成的最后,調(diào)用fullfill來通知異步測(cè)試滿足條件。
- (void)testAsyncFunction{?
????XCTestExpectation?* expectation = [self?expectationWithDescription:@"Just a demo ????expectation,should pass"];?
????//Async?function?when?finished call
????[expectation fullfill];
????[self?waitForExpectationsWithTimeout:10?handler:^(NSError?*error) {?//Do?something?when?time out }]; }
舉例
- (void)testAsyncFunction{
????XCTestExpectation * expectation = [self?expectationWithDescription:@"Just a demo expectation,should pass"];
? ?dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0), ^{ ????????sleep(1);?
????????NSLog(@"Async test");
????????XCTAssert(YES,"should pass");
????????[expectation fulfill];
????});
????[self?waitForExpectationsWithTimeout:10?handler:^(NSError?*error) {?
????????//Do something when time out
?????}];
}
測(cè)試結(jié)果
Test Suite?'Selected tests'?started?at?2015-06-28?05:49:43?+0000?
Test Suite?'DemoTests.xctest'?started?at?2015-06-28?05:49:43?+0000?
Test Suite?'DemoTests'?started?at?2015-06-28?05:49:43?+0000?
Test Case?'-[DemoTests testAsyncFunction]'?started.?2015-06-28?13:49:44.920
?Demo[2157:145428] Async test Test Case?'-[DemoTests testAsyncFunction]'?passed (1.006?seconds). Test Suite?'DemoTests'?passed?at?2015-06-28?05:49:44?+0000.?Executed?1?test,?with?0?failures (0?unexpected)?in?1.006?(1.007)?seconds?
Test Suite?'DemoTests.xctest'?passed?at?2015-06-28?05:49:44?+0000.?Executed?1?test,?with?0?failures (0?unexpected)?in?1.006?(1.009)?seconds?
Test Suite?'Selected tests'?passed?at?2015-06-28?05:49:44?+0000.
選擇Target,然后選擇Test模塊,然后勾選Gather coverage data
然后,在report模塊中,就能看到每一個(gè).m文件的代碼覆蓋情況了。
更新:2017.02.19 增加代碼覆蓋率,對(duì)性能測(cè)試進(jìn)行詳細(xì)的講解。
計(jì)劃下一篇會(huì)講解Mock 測(cè)試以及一些常用的Mock小工具。