單元測試

  • 測試導(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)航之間。
![查看測試用例](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午4.45.25.png)
注意:Xcode 測試目標(biāo)(target)生成的測試包(test bundle)顯示在測試導(dǎo)航(test navigator)處。

![測試目標(biāo)2](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午4.46.15.png)

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

![運(yùn)行測試2](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午4.46.41.png)

灰色菱形代表性能測試 這也就能說明這里為什么沒有通過或是失敗的斷言點(diǎn)擊此處會(huì)出現(xiàn)性能測試結(jié)果面板,此面板允許設(shè)置性能基準(zhǔn)以及編輯基線和最大參數(shù)
![測試結(jié)果面板](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午4.46.50.png)

關(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以外,還可以使用 expectationForPredicateexpectationForNotification

下面這個(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)

![斷點(diǎn)調(diào)試](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午5.50.42.png)

當(dāng)測試方法發(fā)布故障斷言時(shí),此斷點(diǎn)停止測試運(yùn)??行。這樣可以通過在測試代碼中設(shè)置的斷點(diǎn)停止執(zhí)行,快速找到存在問題的位置。
![測試斷點(diǎn)](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午5.51.48.png)

使用命令行運(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

輔助工具

![輔助編輯器](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.06.43.png)

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的測試操作可以啟用代碼覆蓋率。

  1. 在scheme編輯菜單中選擇Edit Scheme。
    ![選擇Edit Scheme](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.12.41.png)
  2. 選擇測試操作。
  3. 啟用代碼覆蓋復(fù)選框以收集覆蓋數(shù)據(jù)。
    ![啟用代碼覆蓋](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.13.52.png)

注意:收集代碼覆蓋率數(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è)文件中的覆蓋百分比。
![代碼覆蓋率](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.15.54.png)

源代碼編輯器展示了文件中代碼的行數(shù)并高亮未執(zhí)行的代碼。它高亮需要覆蓋的代碼區(qū)域而非已經(jīng)覆蓋的區(qū)域。

例如,將指針放在-[Calculator input:]方法上,將顯示一個(gè)按鈕,索引到源代碼。
![索引代碼](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.17.12.png)

覆蓋注釋在右邊顯示,顯示了在測試中代碼某個(gè)特定部分被執(zhí)行的次數(shù)。例如:
![顯示執(zhí)行次數(shù)](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.18.00.png)
input:方法,在測試中被頻繁調(diào)用。然而,有部分方法并未被調(diào)用。在源代碼編輯器中有明顯的標(biāo)記,如圖:
![提示](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.18.07.png)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 編寫測試可不是一項(xiàng)迷人的工作;然而,由于測試可以避免使你的寶貝應(yīng)用程序變成一塊充斥錯(cuò)誤的大垃圾場,所以編寫測試又是...
    cosWriter閱讀 1,709評(píng)論 0 4
  • 原文地址:https://www.raywenderlich.com/150073/ios-unit-testin...
    默默熊閱讀 5,127評(píng)論 1 67
  • 簡介 測試目的:模擬多種可能性,減少錯(cuò)誤,增強(qiáng)健壯性,提高穩(wěn)定性。 測試種類:在iOS中的通常分為單元測試和UI測...
    i順頌時(shí)宜閱讀 9,378評(píng)論 0 39
  • 單元測試不是一個(gè)小工程,需要多用些時(shí)間才能做好,不要希望通過這個(gè)文章就能掌握單元測試,這只是一個(gè)入門,需要自己動(dòng)手...
    勇不言棄92閱讀 8,108評(píng)論 9 60
  • OCUnit(即用XCTest進(jìn)行測試)蘋果自帶的測試框架,GHUnit是一個(gè)可視化的測試框架。(有了它,你可以點(diǎn)...
    呆呆滴木木菇?jīng)?/span>閱讀 801評(píng)論 0 6

友情鏈接更多精彩內(nèi)容