iOS單元測試

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)

    image.png

  • 創(chuàng)建項(xiàng)目成功后年碘,項(xiàng)目目錄下即可看到對應(yīng)的單元測試文件夾(先忽略SimpleProjectUITests UI測試)


    image.png
2.1.2 項(xiàng)目創(chuàng)建后添加
  • 點(diǎn)擊Show the Test navigator選項(xiàng)可以看到現(xiàn)在我們項(xiàng)目中是未添加單元測試的:


    image.png
  • 點(diǎn)擊下方?按鈕屿衅,選中New Unit Test Target選項(xiàng),然后配置參數(shù):
    截屏2020-08-06 下午5.14.26.png

    點(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 簡單使用
    1. 我們在項(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

    1. 然后創(chuàng)建Student對應(yīng)的測試類:StudentTests:
#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
    1. 運(yùn)行測試用例

代碼編輯器邊欄菱形按鈕哗总,測試單個用例
Test 導(dǎo)航欄,測試單個用例
快捷鍵? + U測試全部用例
使用命令行工具 xcodebuild 可以測試單個用例蛋哭,也可以測試全部用例谆趾。

image.png
    1. 觀察測試結(jié)果
image.png
    1. 查看代碼覆蓋率
      打開Edit Scheme:
      image.png

勾選Gather coverage for:

image.png

然后重新,運(yùn)行測試用例来候,觀察結(jié)果:


image.png

3. 如何進(jìn)行性能測試

性能測試通過度量代碼塊執(zhí)行所消耗的時間長短营搅,來衡量是否通過測試。
性能測試會運(yùn)行想要評估的代碼塊十次园欣,收集平均執(zhí)行時間和運(yùn)行的標(biāo)準(zhǔn)偏差休蟹。然后平均值與baseLine進(jìn)行比較以評估成功或失敗鸡挠。

baseLine是我們指定的用來評估測試通過或者失敗的值拣展。我們也可以自己指定一個特定的值备埃。

截屏2020-08-07 下午4.45.20.png

我們可以通過點(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. 異步測試

什么時候需要使用異步測試:

  1. 打開文檔
  2. 在后臺線程中執(zhí)行的服務(wù)和網(wǎng)絡(luò)活動
  3. 執(zhí)行動畫
  4. 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翻譯

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末墨缘,一起剝皮案震驚了整個濱河市星虹,隨后出現(xiàn)的幾起案子镊讼,更是在濱河造成了極大的恐慌宽涌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝶棋,死亡現(xiàn)場離奇詭異卸亮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)玩裙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門嫡良,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人献酗,你說我怎么就攤上這事寝受。” “怎么了罕偎?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵很澄,是天一觀的道長。 經(jīng)常有香客問我颜及,道長甩苛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任俏站,我火速辦了婚禮讯蒲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肄扎。我一直安慰自己墨林,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布犯祠。 她就那樣靜靜地躺著旭等,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衡载。 梳的紋絲不亂的頭發(fā)上搔耕,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音痰娱,去河邊找鬼弃榨。 笑死菩收,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲸睛。 我是一名探鬼主播坛梁,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腊凶!你這毒婦竟也來了划咐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤钧萍,失蹤者是張志新(化名)和其女友劉穎褐缠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體风瘦,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡队魏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了万搔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胡桨。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖瞬雹,靈堂內(nèi)的尸體忽然破棺而出昧谊,到底是詐尸還是另有隱情,我是刑警寧澤酗捌,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布呢诬,位于F島的核電站,受9級特大地震影響胖缤,放射性物質(zhì)發(fā)生泄漏尚镰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一哪廓、第九天 我趴在偏房一處隱蔽的房頂上張望狗唉。 院中可真熱鬧,春花似錦涡真、人聲如沸分俯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澳迫。三九已至局齿,卻和暖如春剧劝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抓歼。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工讥此, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拢锹,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓萄喳,卻偏偏與公主長得像卒稳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子他巨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345