- 測試導(dǎo)航(test navigator)
當(dāng)進(jìn)行測試時(shí),你會(huì)經(jīng)常使用到Xcode的測試導(dǎo)航(test navigator)。
測試導(dǎo)航(test navigator)是workspace的一部分,使創(chuàng)建、管理、運(yùn)行和評(píng)估測試更加加單。通過點(diǎn)擊導(dǎo)航選擇欄中的圖標(biāo)進(jìn)入測試,該圖標(biāo)在問題導(dǎo)航和調(diào)試導(dǎo)航之間。

注意:Xcode 測試目標(biāo)(target)生成的測試包(test bundle)顯示在測試導(dǎo)航(test navigator)處。
- 測試目標(biāo)


點(diǎn)擊測試文件右側(cè)
編譯運(yùn)行測試用例編譯運(yùn)行

綠色為測試通過、紅色為不通過

灰色菱形代表性能測試 這也就能說明這里為什么沒有通過或是失敗的斷言點(diǎn)擊此處會(huì)出現(xiàn)性能測試結(jié)果面板,此面板允許設(shè)置性能基準(zhǔn)以及編輯基線和最大參數(shù)

關(guān)于測試文件方法
- 所有的測試用例方法保證以test開頭
-
setUp在測試用例之前調(diào)用的初始化方法tearDown釋放測試用例資源在每個(gè)測試用例完成后調(diào)用testExample例子testPerformanceExample測試性能的例子,在block內(nèi)寫測試性能的代碼,設(shè)置baseline(基準(zhǔn))和stddev(標(biāo)準(zhǔn)偏差)來判斷方法是否能通過性能測試。
- (void)setUp {
[super setUp];
// Put setup code here. This method is called 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.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
斷言
XCTest
XCTFail(format…) 生成一個(gè)失敗的測試;
XCTAssertNil(a1, format...)為空判斷,a1為空時(shí)通過,反之不通過;
XCTAssertNotNil(a1, format…)不為空判斷,a1不為空時(shí)通過,反之不通過;
XCTAssert(expression, format...)當(dāng)expression求值為TRUE時(shí)通過;
XCTAssertTrue(expression, format...)當(dāng)expression求值為TRUE時(shí)通過;
XCTAssertFalse(expression, format...)當(dāng)expression求值為False時(shí)通過;
XCTAssertEqualObjects(a1, a2, format...)判斷相等,[a1 isEqual:a2]值為TRUE時(shí)通過,其中一個(gè)不為空時(shí),不通過;
XCTAssertNotEqualObjects(a1, a2, format...)判斷不等,[a1 isEqual:a2]值為False時(shí)通過;
XCTAssertEqual(a1, a2, format...)判斷相等(當(dāng)a1和a2是 C語言標(biāo)量、結(jié)構(gòu)體或聯(lián)合體時(shí)使用,實(shí)際測試發(fā)現(xiàn)NSString也可以);
XCTAssertNotEqual(a1, a2, format...)判斷不等(當(dāng)a1和a2是 C語言標(biāo)量、結(jié)構(gòu)體或聯(lián)合體時(shí)使用);
XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...)判斷相等,(double或float類型)提供一個(gè)誤差范圍,當(dāng)在誤差范圍(+/-accuracy)以內(nèi)相等時(shí)通過測試;
XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判斷不等,(double或float類型)提供一個(gè)誤差范圍,當(dāng)在誤差范圍以內(nèi)不等時(shí)通過測試;
XCTAssertThrows(expression, format...)異常測試,當(dāng)expression發(fā)生異常時(shí)通過;反之不通過;(很變態(tài)) XCTAssertThrowsSpecific(expression, specificException, format...) 異常測試,當(dāng)expression發(fā)生specificException異常時(shí)通過;反之發(fā)生其他異?;虿话l(fā)生異常均不通過;
XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...)異常測試,當(dāng)expression發(fā)生具體異常、具體異常名稱的異常時(shí)通過測試,反之不通過;
XCTAssertNoThrow(expression, format…)異常測試,當(dāng)expression沒有發(fā)生異常時(shí)通過測試;
XCTAssertNoThrowSpecific(expression, specificException, format...)異常測試,當(dāng)expression沒有發(fā)生具體異常、具體異常名稱的異常時(shí)通過測試,反之不通過;
XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...)異常測試,當(dāng)expression沒有發(fā)生具體異常、具體異常名稱的異常時(shí)通過測試,反之不通過
特別注意下XCTAssertEqualObjects和XCTAssertEqual。
XCTAssertEqualObjects(a1, a2, format...)的判斷條件是[a1 isEqual:a2]是否返回一個(gè)YES。XCTAssertEqual(a1, a2, format...)的判斷條件是a1 == a2是否返回一個(gè)YES。對(duì)于后者,如果a1和a2都是基本數(shù)據(jù)類型變量,那么只有a1 == a2才會(huì)返回YES。
示例如下:
- 斷言:
- (void)testExample {
//設(shè)置變量和預(yù)期效果
NSUInteger a = 10;NSUInteger b = 15;
NSUInteger c = 24;
//執(zhí)行方法得到
NSUInteger sum = [self sum:a b:b];
//斷言判定實(shí)際值和預(yù)期是否符合
XCTAssertEqual(sum, c,@"測試不通過");
}
-(NSUInteger)sum:(NSUInteger)a b:(NSUInteger)b{
return a+b;
}
- 性能
關(guān)于性能測試:設(shè)置baseline(基準(zhǔn))和stddev(標(biāo)準(zhǔn)偏差)來判斷方法是否能通過性能測試。
就比如說關(guān)于網(wǎng)絡(luò)請(qǐng)求通常我們給出一個(gè)標(biāo)準(zhǔn),多長時(shí)間內(nèi)請(qǐng)求到結(jié)果,能夠接受的偏差是多少。那么就可以設(shè)置一個(gè)基準(zhǔn)盒偏差來驗(yàn)證這個(gè)請(qǐng)求。
- 異步
官方文檔中給出
- (void)testDocumentOpening
{
// 創(chuàng)建一個(gè)expectation對(duì)象
XCTestExpectation *documentOpenExpectation = [self expectationWithDescription:@"document open"];
NSURL *URL = [[NSBundle bundleForClass:[self class]]
URLForResource:@"TestDocument" withExtension:@"mydoc"];
UIDocument *doc = [[UIDocument alloc] initWithFileURL:URL];
[doc openWithCompletionHandler:^(BOOL success) {
XCTAssert(success);
// assert成功后 便會(huì)調(diào)用expectation的fulfill方法,來觸發(fā)下面的handler
[documentOpenExpectation fulfill];
}];
// 在case最后使用這一方法,此時(shí)單測程序會(huì)阻塞到這里;除非達(dá)到了超時(shí)時(shí)間(秒單位)或者是回調(diào)函數(shù)中調(diào)用了fulfill,單測程序才會(huì)結(jié)束
// 若是超時(shí)情況,則認(rèn)為case失?。蝗敉ㄟ^expectation的fulfill觸發(fā),則case通過
[self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
[doc closeWithCompletionHandler:nil];
}];
}
- (void)testAsynExample {
XCTestExpectation *exp = [self expectationWithDescription:@"這里可以是操作出錯(cuò)的原因描述。。。"];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
//模擬這個(gè)異步操作需要2秒后才能獲取結(jié)果,比如一個(gè)異步網(wǎng)絡(luò)請(qǐng)求
sleep(2);
//模擬獲取的異步操作后,獲取結(jié)果,判斷異步方法的結(jié)果是否正確
XCTAssertEqual(@"a", @"a");
//如果斷言沒問題,就調(diào)用fulfill宣布測試滿足
[exp fulfill];
}];
//設(shè)置延遲多少秒后,如果沒有滿足測試條件就報(bào)錯(cuò)
[self waitForExpectationsWithTimeout:3 handler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}
這個(gè)測試肯定是通過的,因?yàn)樵O(shè)置延遲為3秒,而異步操作2秒就除了一個(gè)正確的結(jié)果,并宣布了條件滿足 [exp fulfill],但是當(dāng)我們把延遲改成1秒,這個(gè)測試用例就不會(huì)成功,錯(cuò)誤原因是 expectationWithDescription:@"這里可以是操作出錯(cuò)的原因描述。。。
異步測試除了使用 expectationWithDescription以外,還可以使用 expectationForPredicate和expectationForNotification
下面這個(gè)例子使用expectationForPredicate 測試方法,代碼來自于AFNetworking,用于測試backgroundImageForState方法
- (void)testThatBackgroundImageChanges {
XCTAssertNil([self.button backgroundImageForState:UIControlStateNormal]);
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(UIButton * _Nonnull button, NSDictionary<NSString *,id> * _Nullable bindings) {
return [button backgroundImageForState:UIControlStateNormal] != nil;
}];
[self expectationForPredicate:predicate
evaluatedWithObject:self.button
handler:nil];
[self waitForExpectationsWithTimeout:20 handler:nil];
}
利用謂詞計(jì)算,button是否正確的獲得了backgroundImage,如果正確20秒內(nèi)正確獲得則通過測試,否則失敗。
expectationForNotification 方法 ,該方法監(jiān)聽一個(gè)通知,如果在規(guī)定時(shí)間內(nèi)正確收到通知?jiǎng)t測試通過。
- (void)testAsynExample1 {
[self expectationForNotification:(@"監(jiān)聽通知的名稱xxx") object:nil handler:nil];
[[NSNotificationCenter defaultCenter]postNotificationName:@"監(jiān)聽通知的名稱xxx" object:nil];
//設(shè)置延遲多少秒后,如果沒有滿足測試條件就報(bào)錯(cuò)
[self waitForExpectationsWithTimeout:3 handler:nil];
}
這個(gè)例子也可以用expectationWithDescription實(shí)現(xiàn),只是多些很多代碼而已,但是這個(gè)可以幫助你更好的理解 expectationForNotification 方法和 expectationWithDescription 的區(qū)別。同理,expectationForPredicate方法也可以使用expectationWithDescription實(shí)現(xiàn)。
func testAsynExample1() {
let expectation = expectationWithDescription("監(jiān)聽通知的名稱xxx")
let sub = NSNotificationCenter.defaultCenter().addObserverForName("監(jiān)聽通知的名稱xxx", object: nil, queue: nil) { (not) -> Void in
expectation.fulfill()
}
NSNotificationCenter.defaultCenter().postNotificationName("監(jiān)聽通知的名稱xxx", object: nil)
waitForExpectationsWithTimeout(1, handler: nil)
NSNotificationCenter.defaultCenter().removeObserver(sub)
}
測試失敗的調(diào)試工具
- 斷點(diǎn)調(diào)試
添加測試斷點(diǎn)

當(dāng)測試方法發(fā)布故障斷言時(shí),此斷點(diǎn)停止測試運(yùn)??行。這樣可以通過在測試代碼中設(shè)置的斷點(diǎn)停止執(zhí)行,快速找到存在問題的位置。

使用命令行運(yùn)行測試
使用Xcode命令行工具,你可以使用腳本自動(dòng)構(gòu)建和測試你的項(xiàng)目。
- 例如:
測試本地OS X的MyApp
xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp -destination 'platform=OS X,arch=x86_64'
模擬器上運(yùn)行,使用模擬器可以應(yīng)對(duì)不同的外形因素和操作系統(tǒng)版本:
例如 > xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone,0S=7.0'
測試機(jī)上運(yùn)行:
> xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp -destination 'platform=iOS,name=Development test6s
destination參數(shù)可以連接在一起,一個(gè)命令在指定共享配置目標(biāo)上執(zhí)行測試。例如,下面的命令鏈將前面三個(gè)例子結(jié)合在一起變成一個(gè)命令:
xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp-destination 'platform=OS X,arch=x86_64'-destination 'platform=iOS,name=Development iPod touch'-destination 'platform=Simulator,name=iPhone,OS=9.0'
如果測試失敗,xcodebuild返回非零退出代碼。
關(guān)于命令行運(yùn)行測試,你需要知道一些基本要素。關(guān)于xcodebuild的詳細(xì)信息,在終端應(yīng)用窗口使用man:
man xcodebuild
輔助工具

Test Callers category 。如果你剛剛修復(fù)導(dǎo)致應(yīng)用測試失敗的方法,也許你希望檢查其他測試中調(diào)用的該方法是否運(yùn)行成功。在源代碼編輯器中有問題的方法中,打開assistant editor并從菜單中選擇 Test Classes category。在彈窗菜單中可以導(dǎo)航到任何調(diào)用它的測試方法中,運(yùn)行這些測試方法確?;貧w測試。
Test Classes category。它與Test Callers category.類似,但顯示的是有測試方法的類的列表,可以導(dǎo)航到你正在編輯的類。
代碼覆蓋
代碼覆蓋率是Xcode7的功能,可以在視覺上看到和衡量你的代碼測試覆蓋率。有了代碼覆蓋率,你可以確定測試是否符合你的預(yù)期。
啟用代碼覆蓋率
Xcode的代碼覆蓋率由LLVM支持的測試操作。當(dāng)你啟用代碼覆蓋率,LLVM基于方法和函數(shù)調(diào)用的頻率來收集覆蓋數(shù)據(jù)。代碼覆蓋率選項(xiàng)可以收集單元測試和UI測試正確性和性能數(shù)據(jù),
編輯scheme的測試操作可以啟用代碼覆蓋率。
- 在scheme編輯菜單中選擇Edit Scheme。
 - 選擇測試操作。
- 啟用代碼覆蓋復(fù)選框以收集覆蓋數(shù)據(jù)。

注意:收集代碼覆蓋率數(shù)據(jù)會(huì)導(dǎo)致性能損耗。無論損耗是否顯著,它均會(huì)影響執(zhí)行代碼的線性方式,因此在測試運(yùn)行中啟用代碼覆蓋率,性能結(jié)果依然具有可比性。然而,當(dāng)你正在認(rèn)真評(píng)估測試程序性能時(shí),你應(yīng)該考慮是否啟用代碼覆蓋率。
- 代碼覆蓋率如何符合測試
代碼覆蓋率是用來衡量測試價(jià)值的工具。它回答了以下問題
當(dāng)你運(yùn)行測試時(shí),什么代碼真正運(yùn)行?
多少測試才算足夠?
換句話說,你是否設(shè)計(jì)足夠的測試確保你所有的代碼都檢查了正確性和性能?
代碼的哪部分沒有被測試?
在測試運(yùn)行完成后,Xcode采用LLVM覆蓋數(shù)據(jù)并在報(bào)告導(dǎo)航中創(chuàng)建覆蓋率報(bào)告,參見覆蓋率面板。它顯示了測試的摘要信息,源文件和源文件中的方法列表以及每個(gè)文件中的覆蓋百分比。

源代碼編輯器展示了文件中代碼的行數(shù)并高亮未執(zhí)行的代碼。它高亮需要覆蓋的代碼區(qū)域而非已經(jīng)覆蓋的區(qū)域。
例如,將指針放在-[Calculator input:]方法上,將顯示一個(gè)按鈕,索引到源代碼。

覆蓋注釋在右邊顯示,顯示了在測試中代碼某個(gè)特定部分被執(zhí)行的次數(shù)。例如:

input:方法,在測試中被頻繁調(diào)用。然而,有部分方法并未被調(diào)用。在源代碼編輯器中有明顯的標(biāo)記,如圖:

官方文檔:關(guān)于Xcode測試