單元測試

  • 測試導(dǎo)航(test navigator)

當(dāng)進(jìn)行測試時,你會經(jīng)常使用到Xcode的測試導(dǎo)航(test navigator)。
測試導(dǎo)航(test navigator)是workspace的一部分,使創(chuàng)建、管理、運行和評估測試更加加單。通過點擊導(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)

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

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

灰色菱形代表性能測試 這也就能說明這里為什么沒有通過或是失敗的斷言點擊此處會出現(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釋放測試用例資源在每個測試用例完成后調(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…) 生成一個失敗的測試;

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)合體時使用,實際測試發(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ā)生其他異?;虿话l(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。

示例如下:

  • 斷言:
- (void)testExample {
    //設(shè)置變量和預(yù)期效果
    NSUInteger a = 10;NSUInteger b = 15;
    NSUInteger c = 24;
    //執(zhí)行方法得到
    NSUInteger sum = [self sum:a b:b];
    //斷言判定實際值和預(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ò)請求通常我們給出一個標(biāo)準(zhǔn),多長時間內(nèi)請求到結(jié)果,能夠接受的偏差是多少。那么就可以設(shè)置一個基準(zhǔn)盒偏差來驗證這個請求。

  • 異步

官方文檔中給出

- (void)testDocumentOpening
{
    // 創(chuàng)建一個expectation對象
    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成功后 便會調(diào)用expectation的fulfill方法,來觸發(fā)下面的handler
        [documentOpenExpectation fulfill];
    }];

    // 在case最后使用這一方法,此時單測程序會阻塞到這里;除非達(dá)到了超時時間(秒單位)或者是回調(diào)函數(shù)中調(diào)用了fulfill,單測程序才會結(jié)束
    // 若是超時情況,則認(rèn)為case失敗;若通過expectation的fulfill觸發(fā),則case通過
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        [doc closeWithCompletionHandler:nil];
    }];
}
  - (void)testAsynExample {
    XCTestExpectation *exp = [self expectationWithDescription:@"這里可以是操作出錯的原因描述。。。"];
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperationWithBlock:^{
        //模擬這個異步操作需要2秒后才能獲取結(jié)果,比如一個異步網(wǎng)絡(luò)請求
        sleep(2);
        //模擬獲取的異步操作后,獲取結(jié)果,判斷異步方法的結(jié)果是否正確
        XCTAssertEqual(@"a", @"a");
        //如果斷言沒問題,就調(diào)用fulfill宣布測試滿足
        [exp fulfill];
    }];

    //設(shè)置延遲多少秒后,如果沒有滿足測試條件就報錯
    [self waitForExpectationsWithTimeout:3 handler:^(NSError * _Nullable error) {
        if (error) {
            NSLog(@"Timeout Error: %@", error);
        }
    }];
}

這個測試肯定是通過的,因為設(shè)置延遲為3秒,而異步操作2秒就除了一個正確的結(jié)果,并宣布了條件滿足 [exp fulfill],但是當(dāng)我們把延遲改成1秒,這個測試用例就不會成功,錯誤原因是 expectationWithDescription:@"這里可以是操作出錯的原因描述。。。

異步測試除了使用 expectationWithDescription以外,還可以使用 expectationForPredicateexpectationForNotification

下面這個例子使用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];
}

利用謂詞計算,button是否正確的獲得了backgroundImage,如果正確20秒內(nèi)正確獲得則通過測試,否則失敗。
expectationForNotification 方法 ,該方法監(jiān)聽一個通知,如果在規(guī)定時間內(nèi)正確收到通知則測試通過。

- (void)testAsynExample1 {
    [self expectationForNotification:(@"監(jiān)聽通知的名稱xxx") object:nil handler:nil];
    [[NSNotificationCenter defaultCenter]postNotificationName:@"監(jiān)聽通知的名稱xxx" object:nil];

    //設(shè)置延遲多少秒后,如果沒有滿足測試條件就報錯
    [self waitForExpectationsWithTimeout:3 handler:nil];
}

這個例子也可以用expectationWithDescription實現(xiàn),只是多些很多代碼而已,但是這個可以幫助你更好的理解 expectationForNotification 方法和 expectationWithDescription 的區(qū)別。同理,expectationForPredicate方法也可以使用expectationWithDescription實現(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ào)試

添加測試斷點

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

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

使用命令行運行測試

使用Xcode命令行工具,你可以使用腳本自動構(gòu)建和測試你的項目。

  • 例如:

測試本地OS X的MyApp
xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp -destination 'platform=OS X,arch=x86_64'

模擬器上運行,使用模擬器可以應(yīng)對不同的外形因素和操作系統(tǒng)版本:
例如 > xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone,0S=7.0'

測試機上運行:
> xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp -destination 'platform=iOS,name=Development test6s

destination參數(shù)可以連接在一起,一個命令在指定共享配置目標(biāo)上執(zhí)行測試。例如,下面的命令鏈將前面三個例子結(jié)合在一起變成一個命令:

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)于命令行運行測試,你需要知道一些基本要素。關(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)用的該方法是否運行成功。在源代碼編輯器中有問題的方法中,打開assistant editor并從菜單中選擇 Test Classes category。在彈窗菜單中可以導(dǎo)航到任何調(diào)用它的測試方法中,運行這些測試方法確?;貧w測試。

Test Classes category。它與Test Callers category.類似,但顯示的是有測試方法的類的列表,可以導(dǎo)航到你正在編輯的類。

代碼覆蓋

代碼覆蓋率是Xcode7的功能,可以在視覺上看到和衡量你的代碼測試覆蓋率。有了代碼覆蓋率,你可以確定測試是否符合你的預(yù)期。
啟用代碼覆蓋率

Xcode的代碼覆蓋率由LLVM支持的測試操作。當(dāng)你啟用代碼覆蓋率,LLVM基于方法和函數(shù)調(diào)用的頻率來收集覆蓋數(shù)據(jù)。代碼覆蓋率選項可以收集單元測試和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ù)會導(dǎo)致性能損耗。無論損耗是否顯著,它均會影響執(zhí)行代碼的線性方式,因此在測試運行中啟用代碼覆蓋率,性能結(jié)果依然具有可比性。然而,當(dāng)你正在認(rèn)真評估測試程序性能時,你應(yīng)該考慮是否啟用代碼覆蓋率。

  • 代碼覆蓋率如何符合測試

代碼覆蓋率是用來衡量測試價值的工具。它回答了以下問題

當(dāng)你運行測試時,什么代碼真正運行?
多少測試才算足夠?
換句話說,你是否設(shè)計足夠的測試確保你所有的代碼都檢查了正確性和性能?
代碼的哪部分沒有被測試?
在測試運行完成后,Xcode采用LLVM覆蓋數(shù)據(jù)并在報告導(dǎo)航中創(chuàng)建覆蓋率報告,參見覆蓋率面板。它顯示了測試的摘要信息,源文件和源文件中的方法列表以及每個文件中的覆蓋百分比。
![代碼覆蓋率](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.15.54.png)

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

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

覆蓋注釋在右邊顯示,顯示了在測試中代碼某個特定部分被執(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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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