前言
軟件工程中測試一直是很重要的組成部分,影響著軟件的質(zhì)量和開發(fā)的效率.作為App開發(fā)也毫不例外,雖然在移動設(shè)備上的代碼測試在國內(nèi)還沒有大范圍普及,但許多大型公司或者優(yōu)秀的創(chuàng)業(yè)公司都對代碼測試有很高的要求和看重,最近也在想如何去減少App開發(fā)中帶來的各種Bug和如何去更好地提高App質(zhì)量,心里第一個想法就是寫測試,用測試來說明代碼的執(zhí)行結(jié)果,用測試來驗證代碼的功能.因此最近開始了解相關(guān)iOS單元測試方面的內(nèi)容,學習如何在iOS開發(fā)中寫單元測試.該Session雖然是關(guān)于Testting in Xcode6,但所涉及的內(nèi)容在Xcode7都沒有太多變化, Xcode7主要是增加了UI Test和查看測試覆蓋率的功能, 所以該Session對了解如何在現(xiàn)在Xcode寫單元測試還是很有參考價值.
內(nèi)容
一. 為什么要測試
- 發(fā)現(xiàn)Bug
幫助開發(fā)者盡早地盡多地發(fā)現(xiàn)程序中的Bug,并且提前去修復(fù)它,而不是將程序發(fā)布給客戶后,由客戶反饋各樣的Bug,再去進行修復(fù).
- 擴展
當對程序進行完善或者新功能的開發(fā)時,通過添加新的測試用例或者原來測試用例代碼來檢查新加入的代碼對原程序是否所有影響.
- 維護
程序具有完整的測試用例和測試內(nèi)容讓其他開發(fā)者能更加容易地交接,使更快地熟悉程序中API的行為和功能.
二. 測試的工作流
主流的測試流程有兩種:
第一種,行為驅(qū)動開發(fā)(又稱BDD)的測試
- 代碼實現(xiàn)功能
- 為其行為添加測試
- 驗證代碼通過測試
第二種, 測試驅(qū)動開發(fā)(又稱TDD)的測試?
- 寫無法通過的測試
- 寫能通過測試的代碼
- 重構(gòu)通過測試的代碼去實現(xiàn)功能
測試通過就會測試用例的代碼呈現(xiàn)綠色狀態(tài),而測試失敗就會呈現(xiàn)紅色狀態(tài)
三.如何在Xcode進行測試
使用XCTest
XCTest Framework是Xcode的測試框架,在Xcode5發(fā)布時推出.它提供一個類XCTestCase作為開發(fā)者自己實現(xiàn)測試的基類, 在繼承此類的基礎(chǔ)上實現(xiàn)自己的測試方法, 方法的命名要符合以test開頭的規(guī)范,盡量讓測試方法的名字表達測試的功能和期望的行為,例如testDocumentOpening; 提供了assertion API比如XCTAssertEqual, XCTAssertNotNil...來幫助判斷測試的失敗還是成功.
使用 Test Target
Xcode中獨立的Test Target進行對測試用例代碼的管理, 類似與一般程序的Target, Test Target進行自己的包資源和測試代碼的管理,每個Target都是獨立,互不干擾,新建工程時,Xcode都會默認了配置一個Unit Test Target,(Xcode 7后有多了一個UI Test Target),也允許在原有的工程上直接添加一個或者多個Test Target.
Xcode測試時的注意點
- 針對測試的內(nèi)容,需要手動地import,讓測試進行注入你的App.
- 存在于Test Target的文件,圖片資源,并不在main bundle中,要使用[NSBundle bundleForClass:[MyTest class]]來資源訪問.
如何運行測試用例
針對已經(jīng)寫好的測試用例,運行測試用例的四種方式
- 快捷鍵 Cmd + U,進行當前scheme的所有測試Target的測試
- 點擊測試用例方法的側(cè)邊欄的按鈕,運行當前測試Target用例方法的測試
- 點擊Xcode左邊導(dǎo)航欄的Test Navigator的按鈕,進行當前scheme的所有測試用例的測試
- 使用命令行 xcodebuild運行指定scheme的測試用例
xcodebuild test
-projetc ~/Document/MyApp.xcodeproj
-scheme MyApp
-destinationplatform=iOS,name=iPhone
異步代碼測試
開發(fā)App時往往為了程序的效率使用多線程,對存在耗時的,不必要阻塞主線程的代碼進行異步執(zhí)行,比如代理和Blcok的異步回調(diào), 網(wǎng)絡(luò)請求, 后臺數(shù)據(jù)處理等操作代碼, 使得對異步代碼的測試也顯得比較特殊,XCTest框架有異步代碼的測試也有很好的API支持.
// 1
- (XCTestExpectation *)expectationWithDescription:(NSString *)description;
- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout handler:(nullable XCWaitCompletionHandler)handler;
// 2
- (XCTestExpectation *)expectationForNotification:(NSString *)notificationName object:(nullable id)objectToObserve handler:(nullable XCNotificationExpectationHandler)handler;
-
通過根據(jù)測試行為的描述自定義一個XCTestExpectation對象, 然后設(shè)置異步測試的超時時間以及處理回調(diào)Handler,在進行異步代碼執(zhí)行測試時,進行常規(guī)的測試斷言,然后使用XCTestExpectation類的fulfill方法來響應(yīng)之前定義的XCTestExpectation對象,表示異步測試的結(jié)束,具體執(zhí)行順序如下示例代碼:
XCTestExpectation *expectation = [self expectationWithDescription:"open doc"]; UIDocument *doc = ...; [doc openWithCompletionHandler:^(BOOL success) { XCTAssert(success); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil];
-
使用通知的方式,先利用expectationForNotification:進行異步測試時間的通知注冊,然后設(shè)置超時時間,然后進行異步代碼執(zhí)行的過程中,使用
NSNotificationCenter
的postNotificationName:
方法主動去響應(yīng)之前注冊的通知,具體執(zhí)行順序如下示例代碼:UIDocument *doc = ...; [doc openWithCompletionHandler:^(BOOL success) { XCTAssert(success); [[NSNotificationCenter defaultCenter] postNotificationName:@"OpenDoc" object:nil];
}];
[self expectationForNotification:@"OpenDoc" object:nil handler:nil]; // 提示:設(shè)置Notification Name一定要統(tǒng)一,否則測試會失敗 [self waitForExpectationsWithTimeout:5.0 handler:nil];
單元測試中的性能測試
Xcode為了提供性能的單元測試專門提供了UI和新API,來檢測測試代碼的執(zhí)行時間和性能波動情況;使用- (void)measureBlock:(void (^)(void))block;
在Block中進行性能測試代碼的調(diào)用,里面的代碼會被運行10次,并且記錄平均時間和十次運行的性能波動情況.
存在性能測試失敗的測試用例,就可以通過Profile的TimeProfile工具進行具體性能問題代碼的定位: 在Xcode左邊導(dǎo)航欄的Test Navigator的右擊需要Profile的測試用例,選擇Profile testMethod.
對每一段代碼性能執(zhí)行測試后Xcode會提供Baseline界面, 呈現(xiàn)上一次測試運行的性能耗時分布,第一次運行后需要設(shè)置Baseline作為之后性能測試的標準,在之后的測試過程中,如果新的性能平均分布增加了10%,XCTest就會認定此次性能測試的失敗;
Note: Baseline的信息保存在設(shè)備中,同樣的測試代碼,針對真機性能代碼測試的Baseline不會在模擬器中測試時使用.
對于性能代碼的測試, Xcode還提供了性能測試結(jié)果的標準偏差(STDDEV),若果偏差超過上次偏差平均值的10%,(可手動調(diào)整)就會被認定測試失敗, 而當偏差不到0.1秒就會被XCTest忽略, Session也說明影響STDDEV的因素主要有: 針對文件或者網(wǎng)絡(luò)的IO操作; 每一次回調(diào)時進行不同的工作; 系統(tǒng)處理進程時的忙碌,這些都可能使得STDDEV變高.
為了能更真實地進行代碼的性能測試,Session建議只對需要進行性能測試的代碼進行測試,其他關(guān)聯(lián)性小的代碼進行不要去影響測試結(jié)果,提供了一種更好的使用measureBlock
的方式,如下實例代碼
[self measureMetrics:@[XCTPerformanceMetric_WallClockTime] automaticallyStartMeasuring:NO forBlock:^{
for (NSInteger index = 1; index < 10000; index++) {
[self startMeasuring];
NSString *string = [NSString stringWithFormat:@"%ld==", index];
[self stopMeasuring];
// 只在需要測試的地方使用start和stop Mesure方法;
[array addObject:string];
}
}];
總結(jié)
雖然軟件工程中沒有真正的"銀彈",測試也不是萬能的,不會讓程序開發(fā)的Bug多一一消除,但保持良好的軟件編程習慣,學會為自己的代碼寫測試,盡可能保證代碼的正確和簡潔,不論是App開發(fā)還是其他領(lǐng)域的開發(fā),對于開發(fā)人員都是一種編程能力的提現(xiàn),以后嘗試多寫寫測試,可以從網(wǎng)絡(luò)層或者數(shù)據(jù)層開始嘗試,更加容易些.