1.介紹
在講XCTest之前我們先來了解一下單元測試讽挟。單元測試(unit testing)耽梅,是指對軟件中的最小可測試單元進(jìn)行檢查和驗(yàn)證,通過開發(fā)者編寫代碼去驗(yàn)證被測代碼是否正確的一種手段拆檬,例如編寫一個測試函數(shù)去測試某一功能函數(shù)是否能正確執(zhí)行達(dá)到預(yù)期效果妥凳。在實(shí)際項(xiàng)目開發(fā)中使用單元測試可以提高軟件的質(zhì)量逝钥,也可以盡量早的發(fā)現(xiàn)代碼中存在的問題加以修正。
2. 簡單使用
XCTest是Xcode自帶的單元測試框架持际,我們可以使用該框架做功能性代碼的白盒單元測試哗咆,以自測并增強(qiáng)代碼健壯性晌柬。
2.1 項(xiàng)目中添加XCTest
2.1.1 創(chuàng)建項(xiàng)目時勾選該選項(xiàng)
-
創(chuàng)建項(xiàng)目時勾選
Include Unit Tests
選項(xiàng)
-
創(chuàng)建項(xiàng)目成功后年碘,項(xiàng)目目錄下即可看到對應(yīng)的單元測試文件夾(先忽略SimpleProjectUITests UI測試)
2.1.2 項(xiàng)目創(chuàng)建后添加
-
點(diǎn)擊Show the Test navigator選項(xiàng)可以看到現(xiàn)在我們項(xiàng)目中是未添加單元測試的:
- 點(diǎn)擊下方?按鈕屿衅,選中
New Unit Test Target
選項(xiàng),然后配置參數(shù):
點(diǎn)擊finish
即可涡尘。
2.2 方法簡單介紹
現(xiàn)在只有一個.m文件考抄,里面有4個方法:
// 在每一個測試方法調(diào)用前,都會被調(diào)用
// 用來初始化 test 用例的一些初始值
- (void)setUp {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
// 在每一個測試方法調(diào)用后,都會被調(diào)用
// 用來重置 test 方法的數(shù)值
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (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.
}];
}
在編寫測試代碼時挑势,需要知道以下幾點(diǎn):
setUp方法
setUp
方法會在XCTestCase
的測試方法每次調(diào)用之前調(diào)用啦鸣,所以可以把一些測試代碼需要用的初始化代碼和全局變量寫在這個方法里;tearDown
在每個單元測試方法執(zhí)行完畢后诫给,XCTest
會執(zhí)行tearDown
方法,所以可以把需要測試完成后銷毀的內(nèi)容寫在這個里凫碌,以便保證下面的測試不受本次測試影響測試用例
所有測試的方法都需要以test
為前綴進(jìn)行命名胃榕,比如- (void)testExample
為業(yè)務(wù)類創(chuàng)建測試類
對于每一個業(yè)務(wù)類勋又,我們都會有一個對應(yīng)的測試類,所有的測試類需要繼承XCTestCase
楔壤,比如:NetService
對應(yīng)NetServiceTest
鹤啡,如果類的內(nèi)容太多蹲嚣,可以通過Category
進(jìn)行分類隙畜,如果某個方法暫時不想測試了,可以加一個Disable
前綴您朽。
2.3 簡單使用
- 我們在項(xiàng)目里面創(chuàng)建一個Student類:
// Student.h 文件
@interface Student : NSObject
- (NSInteger)studyAddA:(NSInteger)a b:(NSInteger)b;
- (NSInteger)studyDeleteA:(NSInteger)a b:(NSInteger)b;
@end
// Student.m 文件
#import "Student.h"
@implementation Student
- (NSInteger)studyAddA:(NSInteger)a b:(NSInteger)b{
NSInteger result = a + b;
return result;
}
- (NSInteger)studyDeleteA:(NSInteger)a b:(NSInteger)b{
NSInteger result = a - b;
return result;
}
@end
- 然后創(chuàng)建
Student
對應(yīng)的測試類:StudentTests
:
- 然后創(chuàng)建
#import "Student.h"
@interface StudentTests : XCTestCase
@property (nonatomic, strong) Student *student;
@end
@implementation StudentTests
- (void)setUp {
self.student = [Student new];
}
- (void)tearDown {
self.student = nil;
}
- (void)testStudentAdd {
NSInteger result = [self.student studyAddA:2 b:3];
XCTAssert(result == 5, @"結(jié)果計算出錯");
}
@end
- 運(yùn)行測試用例
代碼編輯器邊欄菱形按鈕哗总,測試單個用例
Test 導(dǎo)航欄,測試單個用例
快捷鍵? + U測試全部用例
使用命令行工具 xcodebuild 可以測試單個用例蛋哭,也可以測試全部用例谆趾。
- 觀察測試結(jié)果
- 查看代碼覆蓋率
打開Edit Scheme
:
- 查看代碼覆蓋率
勾選Gather coverage for
:
然后重新,運(yùn)行測試用例来候,觀察結(jié)果:
3. 如何進(jìn)行性能測試
性能測試通過度量代碼塊執(zhí)行所消耗的時間長短营搅,來衡量是否通過測試。
性能測試會運(yùn)行想要評估的代碼塊十次园欣,收集平均執(zhí)行時間和運(yùn)行的標(biāo)準(zhǔn)偏差休蟹。然后平均值與baseLine進(jìn)行比較以評估成功或失敗鸡挠。
baseLine是我們指定的用來評估測試通過或者失敗的值拣展。我們也可以自己指定一個特定的值备埃。
我們可以通過點(diǎn)擊measureBlock:
方法左邊菱形圓心 icon 按脚,來設(shè)置Baseline,設(shè)置之后需要點(diǎn)擊save保存。之后再執(zhí)行測試用例時唯沮,如果成功,左邊的icon會從圓心變成一個 ?萌庆。
3.1 如何進(jìn)行性能測試
相關(guān) API :
- measureBlock:
- (void)testPerformanceOfMyFunction {
[self measureBlock:^{
// Do that thing you want to measure.
MyFunction();
}];
}
- measureMetrics:automaticallyStartMeasuring:forBlock:
- (void)testMyFunction2_WallClockTime {
[self measureMetrics:[self class].defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
// Do setup work that needs to be done for every iteration but you don't want to measure before the call to -startMeasuring
SetupSomething();
[self startMeasuring];
// Do that thing you want to measure.
MyFunction();
[self stopMeasuring];
// Do teardown work that needs to be done for every iteration but you don't want to measure after the call to -stopMeasuring
TeardownSomething();
}];
}
4. 異步測試
什么時候需要使用異步測試:
- 打開文檔
- 在后臺線程中執(zhí)行的服務(wù)和網(wǎng)絡(luò)活動
- 執(zhí)行動畫
- UI 測試時
4.1 異步測試XCTestExpectation
異步測試分為3個部分: 新建期望 践险、等待期望被履行 和 履行期望 吹菱。
- XCTestExpectation:測試期望鳍刷,可以由測試類持有,也可以自己持有,自己持有測試期望時靈活性更好一些蚌成,你可以選擇等待哪些期望芹缔。
// 測試類持有的初始化方法
XCTestExpectation *expect1 = [self expectationWithDescription:@"asyncTest1"];
// 自己持有的初始化方法
XCTestExpectation *expect2 = [[XCTestExpectation alloc] initWithDescription:@"asyncTest3"];
- waitForExpectations:timeout: :等待異步的期望代碼執(zhí)行最欠,根據(jù)初始化方式不同惩猫,等待的方法不同。
// 測試類持有時的等待方法
[self waitForExpectationsWithTimeout:10.0 handler:nil];
// 自己持有時的等待方法
[self waitForExpectations:@[expect3] timeout:10.0];
- fulfill :履行期望拌阴,并且適當(dāng)加入XCTAssertTrue等斷言奶镶,來驗(yàn)證測試結(jié)果厂镇。
XCTestExpectation *expect3 = [[XCTestExpectation alloc] initWithDescription:@"asyncTest3"];
[TTFakeNetworkingInstance requestWithService:apiRecordList completionHandler:^(NSDictionary *response) {
XCTAssertTrue([response[@"code"] isEqualToString:@"200"]);
[expect3 fulfill];
}];
[self waitForExpectations:@[expect3] timeout:10.0];
4.2 異步測試XCTWaiter
XCTWaiter是 2017 年新增的異步測試方案,可以通過代理方式來處理異常情況酌媒。
XCTWaiter *waiter = [[XCTWaiter alloc] initWithDelegate:self];
XCTestExpectation *expect4 = [[XCTestExpectation alloc] initWithDescription:@"asyncTest3"];
[TTFakeNetworkingInstance requestWithService:@"product.list" completionHandler:^(NSDictionary *response) {
XCTAssertTrue([response[@"code"] isEqualToString:@"200"]);
expect4 fulfill];
}];
XCTWaiterResult result = [waiter waitForExpectations:@[expect4] timeout:10 enforceOrder:NO];
XCTAssert(result == XCTWaiterResultCompleted, @"failure: %ld", result);
XCTWaiterDelegate:如果委托是XCTestCase實(shí)例馍佑,下方代理被調(diào)用時會報告為測試失敗。
// 如果有期望超時茵臭,則調(diào)用舅世。
- (void)waiter:(XCTWaiter *)waiter didTimeoutWithUnfulfilledExpectations:(NSArray<XCTestExpectation *> *)unfulfilledExpectations;
// 當(dāng)履行的期望被強(qiáng)制要求按順序履行雏亚,但期望以錯誤的順序被履行,則調(diào)用查辩。
- (void)waiter:(XCTWaiter *)waiter fulfillmentDidViolateOrderingConstraintsForExpectation:(XCTestExpectation *)expectation requiredExpectation:(XCTestExpectation *)requiredExpectation;
// 當(dāng)某個期望被標(biāo)記為被倒置网持,則調(diào)用。
- (void)waiter:(XCTWaiter *)waiter didFulfillInvertedExpectation:(XCTestExpectation *)expectation;
// 當(dāng) waiter 在 fullfill 和超時之前被打斷萍倡,則調(diào)用辟汰。
- (void)nestedWaiter:(XCTWaiter *)waiter wasInterruptedByTimedOutWaiter:(XCTWaiter *)outerWaiter;
5. 斷言記錄
在寫測試用例的時候帖汞,我們可以使用斷言,下面是記錄一下:
XCTFail(format…) 生成一個失敗的測試填硕;
XCTAssertNil(a1, format...)為空判斷鹿鳖,a1為空時通過翅帜,反之不通過;
XCTAssertNotNil(a1, format…)不為空判斷绣版,a1不為空時通過,反之不通過诈唬;
XCTAssert(expression, format...)當(dāng)expression求值為TRUE時通過缩麸;
XCTAssertTrue(expression, format...)當(dāng)expression求值為TRUE時通過杭朱;
XCTAssertFalse(expression, format...)當(dāng)expression求值為False時通過;
XCTAssertEqualObjects(a1, a2, format...)判斷相等八酒,[a1 isEqual:a2]值為TRUE時通過刃唐,其中一個不為空時,不通過衔瓮;
XCTAssertNotEqualObjects(a1, a2, format...)判斷不等荒澡,[a1 isEqual:a2]值為False時通過单山;
XCTAssertEqual(a1, a2, format...)判斷相等(當(dāng)a1和a2是 C語言標(biāo)量幅疼、結(jié)構(gòu)體或聯(lián)合體時使用,實(shí)際測試發(fā)現(xiàn)NSString也可以)爽篷;
XCTAssertNotEqual(a1, a2, format...)判斷不等(當(dāng)a1和a2是 C語言標(biāo)量、結(jié)構(gòu)體或聯(lián)合體時使用)铡溪;
XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...)判斷相等泪喊,(double或float類型)提供一個誤差范圍袒啼,當(dāng)在誤差范圍(+/-accuracy)以內(nèi)相等時通過測試纬纪;
XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判斷不等滑肉,(double或float類型)提供一個誤差范圍靶庙,當(dāng)在誤差范圍以內(nèi)不等時通過測試;
XCTAssertThrows(expression, format...)異常測試按声,當(dāng)expression發(fā)生異常時通過恬吕;反之不通過铐料;(很變態(tài)) XCTAssertThrowsSpecific(expression, specificException, format...) 異常測試,當(dāng)expression發(fā)生specificException異常時通過柒凉;反之發(fā)生其他異陈耍或不發(fā)生異常均不通過愧沟;
XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...)異常測試,當(dāng)expression發(fā)生具體異常林艘、具體異常名稱的異常時通過測試混坞,反之不通過究孕;
XCTAssertNoThrow(expression, format…)異常測試,當(dāng)expression沒有發(fā)生異常時通過測試镶殷;
XCTAssertNoThrowSpecific(expression, specificException, format...)異常測試泳猬,當(dāng)expression沒有發(fā)生具體異常、具體異常名稱的異常時通過測試指郁,反之不通過拷呆;
XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...)異常測試,當(dāng)expression沒有發(fā)生具體異常腰懂、具體異常名稱的異常時通過測試项秉,反之不通過
特別注意下XCTAssertEqualObjects和XCTAssertEqual娄蔼。
XCTAssertEqualObjects(a1, a2, format...)的判斷條件是[a1 isEqual:a2]是否返回一個YES。
XCTAssertEqual(a1, a2, format...)的判斷條件是a1 == a2是否返回一個YES锚沸。
對于后者涕癣,如果a1和a2都是基本數(shù)據(jù)類型變量坠韩,那么只有a1 == a2才會返回YES。例如
合理使用測試基類和測試工具類绽昼,可以避免大量重復(fù)測試代碼。時間轉(zhuǎn)換工具類是一個沒有外部依賴的類目溉,當(dāng)一些對外部有依賴的類需要測試時缭付,可以嘗試 OCMock 柿估,它能幫助你模擬數(shù)據(jù)。另外陷猫,當(dāng)你覺得測試框架提供的斷言方法無法滿足你時秫舌,也可以試著使用 OCHamcrest 的妖。
6. 未完待續(xù)
簡單的記錄一下,還有很多等待發(fā)現(xiàn)足陨。嫂粟。。
7. 參考
iOS開發(fā)之XCTest
官方文檔翻譯
在XCode中使用XCTest
iOS 單元測試和 UI 測試快速入門
官方文檔
XCTest 測試實(shí)戰(zhàn)
OCMock翻譯