iOS 單元測試和 UI 測試快速入門

xietao3發表於2018-12-20

前言

平時寫完業務程式碼的時候都會去自己測試一遍,後面每次有修改都需要重複測,不管是一個業務流程還是一個工具類,其實都可以通過測試框架來幫助我們完成測試,特別是一些頻繁修改的程式碼,更需要嚴謹的測試。在淺淺地對自動化測試有一些瞭解時,覺得寫測試程式碼挺耗時間,但其實對後期的幫助是非常大的,可以根據自己的實際情況來決定哪些地方需要加入自動化測試。

本文內容適合剛接觸 iOS 自動化測試的同學,基本內容來自於各年 WWDC 的多個 Sessions,本文程式碼部分基於我的一個學習 Demo,喜歡的可以瞭解一下。本文介紹的大致內容包括:

  • 單元測試
  • UI 測試
  • 擴充 Tips
  • 工程可測性

一、單元測試

1.1 加入測試 Target

在新建專案時,勾選Include Unit TestsInclude UI Tests,即可為專案新增單元測試和 UI 測試。

在新增測試程式碼時,你需要遵守一些最基本的規則:

  • 所有的測試類需要繼承XCTestCase

    @interface TTTestCase : XCTestCase
    複製程式碼
  • 測試方法命名以 test 開始

    - (void)testThatMyFunctionWorks
    複製程式碼
  • 用 Assertion API 進行驗證是否通過

    XCTAssertEqual(value, expectedValue)
    複製程式碼

1.2 啟動測試

單元測試的結構:

  • step1:準備輸入
  • step2:執行正在測試的程式碼
  • step3:驗證輸出
// 準備輸入
NSString *dateString = @"2000-01-01";

// 需要測試的方法
BOOL isToday = [TTDateFormatter isTodayWithDateString:dateString];

// 驗證輸出
XCTAssert(isToday, @"isToday false");
複製程式碼

以上三個部分的程式碼準備完成後即可開始測試,啟動的方式有很多種,可以根據你的實際情況選擇以下方式:

  • 程式碼編輯器邊欄菱形按鈕,測試單個用例
  • Test 導航欄,測試單個用例
  • 快捷鍵⌘ + U測試全部用例
  • 使用命令列工具 xcodebuild 可以測試單個用例,也可以測試全部用例。

123

1.3 效能測試

效能測試通過度量程式碼塊執行所消耗的時間長短,來衡量是否通過測試。

1.3.1 如何進行效能測試

相關 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();
        }];
    }
    複製程式碼

1.3.2 設定基準線

所有的效能測試需要設定一個Baseline來驗證是否通過測試,沒有設定的會提示No baseline average for Time

1.2

我們可以通過點選measureBlock:方法左邊菱形圓心 icon ,來設定Baseline,設定之後需要點選save儲存。之後再執行測試用例時,如果成功,左邊的icon會從圓心變成一個 ✅。

1.2

1.4 非同步測試

什麼時候需要使用非同步測試:

  • 開啟文件
  • 在後臺執行緒中執行的服務和網路活動
  • 執行動畫
  • UI 測試時

1.4.1 非同步測試 XCTestExpectation

非同步測試分為3個部分: 新建期望等待期望被履行履行期望

  • XCTestExpectation :測試期望,可以由測試類持有,也可以自己持有,自己持有測試期望時靈活性更好一些,你可以選擇等待哪些期望。

    // 測試類持有的初始化方法
    XCTestExpectation *expect1 = [self expectationWithDescription:@"asyncTest1"];
    
    // 自己持有的初始化方法
    XCTestExpectation *expect2 = [[XCTestExpectation alloc] initWithDescription:@"asyncTest3"];
    複製程式碼
  • waitForExpectations:timeout: :等待非同步的期望程式碼執行,根據初始化方式不同,等待的方法不同。

    // 測試類持有時的等待方法
    [self waitForExpectationsWithTimeout:10.0 handler:nil];
    
    // 自己持有時的等待方法
    [self waitForExpectations:@[expect3] timeout:10.0];
    複製程式碼
  • fulfill :履行期望,並且適當加入XCTAssertTrue等斷言,來驗證測試結果。

    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];
    複製程式碼

1.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例項,下方代理被呼叫時會報告為測試失敗。

// 如果有期望超時,則呼叫。 
- (void)waiter:(XCTWaiter *)waiter didTimeoutWithUnfulfilledExpectations:(NSArray<XCTestExpectation *> *)unfulfilledExpectations;

// 當履行的期望被強制要求按順序履行,但期望以錯誤的順序被履行,則呼叫。
- (void)waiter:(XCTWaiter *)waiter fulfillmentDidViolateOrderingConstraintsForExpectation:(XCTestExpectation *)expectation requiredExpectation:(XCTestExpectation *)requiredExpectation;

// 當某個期望被標記為被倒置,則呼叫。 
- (void)waiter:(XCTWaiter *)waiter didFulfillInvertedExpectation:(XCTestExpectation *)expectation;

// 當 waiter 在 fullfill 和超時之前被打斷,則呼叫。 
- (void)nestedWaiter:(XCTWaiter *)waiter wasInterruptedByTimedOutWaiter:(XCTWaiter *)outerWaiter;
複製程式碼

1.5 檢視測試結果

在執行測試用例後,Xcode 會返回給我們測試結果,可以通過一下途徑檢視:

  • Test 導航欄
  • Issue 導航欄
  • 程式碼編輯器左邊欄
  • Report 導航欄

檢視測試結果

除此之外,我們還可以在 Report 導航欄中檢視更加詳細的測試報告:

  • 測試通過/失敗
  • 失敗原因
  • 效能指標
  • 截圖
  • 巢狀的 activities
  • 測試覆蓋率

iOS 單元測試和 UI 測試快速入門

1.6 進行單元測試

我新建一個時間工具類,幫助我轉換時間,在使用之前,我們需要先進行測試,以保證功能完整且正確。

這個工具類有以下 4 個公共方法,

@interface TTDateFormatter : NSDate

+ (NSString *)stringFormatWithDate:(NSDate *)date;

+ (NSDate *)dateFormatWithString:(NSString *)dateString;

+ (BOOL)isTodayWithDateString:(NSString *)dateString;

+ (NSString *)getHowLongAgoWithTimeStamp:(NSTimeInterval)timeStamp;

@end
複製程式碼

針對一個工具類的測試我們可以新建一個TTDateFormatterTests測試類,繼承一個測試基類。再根據不同的方法寫不同的測試方法。如果有ifswitch等條件語句導致邏輯分支的程式碼,儘量使各個邏輯分支都能測試到,可以配合程式碼覆蓋率來檢查哪些邏輯分支未測試。

@interface TTDateFormatterTests : TTTestCase
@end

@implementation TTDateFormatterTests

- (void)testDateFormatter {
    NSString *originDateString = @"2018-06-06 20:20:20";
    NSDate *date = [TTDateFormatter dateFormatWithString:originDateString];
    NSString *dateString = [TTDateFormatter stringFormatWithDate:date];
    XCTAssertEqualObjects(dateString, originDateString);
}

- (void)testDateFormatterIsToday {
    NSString *dateString = [TTDateFormatter stringFormatWithDate:[NSDate date]];
    XCTAssertTrue([TTDateFormatter isTodayWithDateString:dateString]);
    XCTAssertFalse([TTDateFormatter isTodayWithDateString:@"2000-01-01"]);
}

- (void)testDateFormatterHowLongAgo {
	// 該方法中包含一個 switch ,要保證 switch 每個邏輯分支都測試到,所以需要多個測試。
    NSDate *now = [NSDate date];
    NSString *secAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 10 * sec];
    XCTAssertEqualObjects(secAgo, @"10秒前");
    
    NSString *minAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 15 * min];
    XCTAssertEqualObjects(minAgo, @"15分鐘前");
    
    NSString *hourAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 20 * hour];
    XCTAssertEqualObjects(hourAgo, @"20小時前");

    NSString *dayAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 25 * hour];
    XCTAssertEqualObjects(dayAgo, @"1天前");

    NSString *daysAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 50 * hour];
    XCTAssertEqualObjects(daysAgo, @"2天前");

    NSString *longTimeAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:1544002463];
    XCTAssertEqualObjects(longTimeAgo, @"2018-12-05 17:34:23");
}
@end
複製程式碼

合理使用測試基類和測試工具類,可以避免大量重複測試程式碼。時間轉換工具類是一個沒有外部依賴的類,當一些對外部有依賴的類需要測試時,可以嘗試 OCMock ,它能幫助你模擬資料。另外,當你覺得測試框架提供的斷言方法無法滿足你時,也可以試著使用 OCHamcrest

二、UI 測試

什麼時候需要使用 UI 測試:

  • 單元測試無法覆蓋時的補充方案
  • 單元測試更精準
  • UI 測試覆蓋面的更全

UI 測試的步驟:

  • step1:與要測試或與邏輯有關的 UI 進行互動
  • step2:驗證 UIelements 屬性和狀態

2.1 UI Recording

通過 UI Recording ,可以將你操作手機的行為記錄下來,並且轉換成程式碼,可以幫助你快速生成 UI 測試程式碼。

選中 UI 測試類,你能再下方看到一個小紅點,點選小紅點開始錄製你的互動。

iOS 單元測試和 UI 測試快速入門

在你進行互動時,Xcode 會自動轉化成程式碼,你可以藉此建立新的測試程式碼,也可以以此擴充已經存在的測試程式碼。當然它也不是十分完美,並不是總能如你所願,還需要你做一些處理,比如說自動生成的程式碼過於繁瑣,你可以用一些更簡潔的程式碼實現。即使這樣,UI Recording 也是非常高效的方式。

2.2 UI 測試相關的類

2.2.1 XCUIApplication

XCUIApplication可以返回一個應用程式例項,然後你就可以通過測試程式碼啟動應用程式。

// 返回 UI 測試 Target 設定中選中的 Target Application 的例項
- (instancetype)init;

// 根據 bundleId 返回一個應用程式例項
- (instancetype)initWithBundleIdentifier:(NSString *)bundleIdentifier;

// 啟動應用程式
- (void)launch;

// 將應用程式喚醒至前臺,在多程式聯合測試下會用到 
- (void)activate;

// 結束一個正在執行的應用程式
- (void)terminate;
複製程式碼

2.2.2 XCUIElement

應用程式中的 UI 控制元件,控制元件型別多樣,可能是Button,Cell,Window等等。該類例項有很多模擬互動的方法,如tap模擬使用者點選事件,swipe模擬滑動事件,typeText:模擬使用者輸入內容。

在 UI 測試中我們需要找到某個空間,可以通過他們的型別來縮小範圍,比如當前頁面有且只有一個UITextView控制元件,你可以通過以下程式碼來獲取:

XCUIApplication *app = [[XCUIApplication alloc] init];
[app launch];

// 如果是 Cell 則對應 app.cells 
// firstMatch 返回第一個符合的控制元件
XCUIElement *textView = app.textViews.firstMatch;

// 模擬使用者在 textView 輸入內容
[textView typeText:@"input string"];
複製程式碼

另外還有一種方式通過 Accessibility identifer, label, title 等等方式來定位對應的控制元件,如尋找一個名為 Add 的button

// 需要勾選 Accessibility Enabled ,並且在 Label 一欄填入 Add
XCUIElement *addButton = app.buttons[@"add"];
// 模擬使用者點選按鈕
[addButton tap];
複製程式碼

iOS 單元測試和 UI 測試快速入門

通過型別加 identifier 的方式來定位的控制元件元素的方式,可以滿足大多數場景。

2.2.3 XCUIElementQuery

XCUIElementQuery是一個用來定位控制元件元素的類,一般是一組符合篩選條件的元素集合。如app.buttons即返回 XCUIElementQuery 例項,是包含了當前所有的button的集合,你可以再通過 XCUIElementQuery的方法做下一步的篩選。

XCUIElementQuery 常見定位元素的方法:

  • count:匹配的數量;

    // 當 navigationBars 的 count 等於 1 時,你可以直接定位到 navigationBar
    app.navigationBars.element 
    複製程式碼
  • subscripting:通過 id 來定位

    table.staticTexts["Groceries"] 
    複製程式碼
  • index:通過元素的下標來定位

    table.staticTexts.elementAtIndex(0) 
    複製程式碼

定位元素除了利用元素型別、Accessibility Identifiers,Predicates 等篩選方法,還可以結合巢狀的層級關係來幫助定位。

2.3 進行 UI 測試

要進行 UI 測試需要以下幾個步驟:

  • step1:新建一個 UI 測試 Target。
  • step2:使用 UI Recording 或手寫程式碼,定位 UI 元素,並且模擬使用者互動事件。
  • step3:加入XCTAssert等斷言邏輯,驗證測試是否通過。
let app = XCUIApplication()
// 啟動 app
app.launch()

// 定位元素
let addButton = app.buttons[“Add”]

// 模擬使用者互動事件
addButton.tap()

// 驗證測試是否通過
XCTTAssertionEqual(app.tables.cells.cout, 1)
複製程式碼

大多數 UI 測試都是基於使用者行為驅動,根據設計好的使用者的操作流程,測試整個流程的結果。我設計了一個簡單的筆記,主要有 3 步操作,分別是建立筆記、展示筆記和刪除筆記,下面一起來看看如何進行測試。

iOS 單元測試和 UI 測試快速入門

// 測試主流程
- (void)testMainFlow {
	// 啟動 app
    XCUIApplication *app = [[XCUIApplication alloc] init];
    [app launch];
    
    // 新增筆記
    [self addRecordWithApp:app msg:@"今天天氣真好!?"];
    [self addRecordWithApp:app msg:@"今天詹姆斯特別給力,帶領球隊走向勝利。✌️"];
	
    while (app.cells.count > 0) {
    	  // 刪除筆記  
        [self deleteFirstRecordWithApp:app];
    }
}

/**
 新增筆記

 @param app app 例項
 @param msg 筆記內容
 */
- (void)addRecordWithApp:(XCUIApplication *)app msg:(NSString *)msg {
	// 暫存當前 cell 數量
    NSInteger cellsCount = app.cells.count;
    
    // 設定一個預期 判斷 app.cells 的 count 屬性會等於 cellsCount+1, 等待直至失敗,如果符合則不再等待
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"count == %d",cellsCount+1];
    [self expectationForPredicate:predicate evaluatedWithObject:app.cells handler:nil];


	 // 定位導航欄+號按鈕,點選進入新增筆記頁面 
    XCUIElement *addButton = app.navigationBars[@"Record List"].buttons[@"Add"];
    [addButton tap];
    
    // 測試 未輸入任何內容點選儲存
    [app.navigationBars[@"Write Anything"].buttons[@"Save"] tap];
    
    // 定位文字輸入框 輸入內容
    XCUIElement *textView = app.textViews.firstMatch;
    [textView typeText:msg];
    
    // 儲存
    [app.navigationBars[@"Write Anything"].buttons[@"Save"] tap];
        
    // 等待預期
    [self waitShortTimeForExpectations];
}

/**
 刪除最近一個筆記

 @param app app 例項
 */
- (void)deleteFirstRecordWithApp:(XCUIApplication *)app {
    NSInteger cellsCount = app.cells.count;
    
    
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"count == %d",cellsCount-1];
    // 設定一個預期 判斷 app.cells 的 count 屬性會等於 cellsCount-1, 等待直至失敗,如果符合則不再等待
    [self expectationForPredicate:predicate evaluatedWithObject:app.cells handler:nil];

	// 定位到 cell 元素
    XCUIElement *firstCell = app.cells.firstMatch;
    
    // 左滑出現刪除按鈕
    [firstCell swipeLeft];
    
    // 定位刪除按鈕
    XCUIElement *deleteButton = [app.buttons matchingIdentifier:@"Delete"].firstMatch;
    
    // 點選刪除按鈕
    if (deleteButton.exists) {
        [deleteButton tap];        
    }
    
    // 等待預期
    [self waitShortTimeForExpectations];

}
複製程式碼

在上面的邏輯中涉及到非同步的請求,我們可以通過利用expectationForPredicate:evaluatedWithObject:handler:方法監聽app.cellscount屬性,當滿足NSPredicate條件時,expectation相當於自動fullfill。如果一直不滿足條件,會一直等待直至超時,除此之外還可以用通知和 KVO 的方式實現。

測試過程:

iOS 單元測試和 UI 測試快速入門

三、 擴充 Tips

3.1 多應用聯合測試

多應用聯合測試時,依賴XCUIApplication類的以下 2 個方法:

  • initWithBundleIdentifier:
  • activate

前者可以根據 BundleId 獲取其他 App 的例項,讓我們可以啟動其他 App。後者可以讓 App 從後臺切換至前臺,在多應用間切換。簡單實現程式碼如下:

// 返回 UI 測試 Target 設定中選中的 Target Application 的例項
XCUIApplication *ttApp = [[XCUIApplication alloc] init];

// 使用 BundleId 獲得另外一個 App 例項
XCUIApplication *anotherApp = [[XCUIApplication alloc] initWithBundleIdentifier:@"Another.App.BundleId"];

// 先啟動我們的主 App
[ttApp launch];

// 做一系列測試

// 啟動另一個 App
[anotherApp launch];

// 做一系列測試

// 回到我們的主 App (在 App 未啟動的情況下調 activate 會讓 App 啟動)
[ttApp activate];
複製程式碼

3.2 邏輯複雜場景下的 Activities

在一些邏輯比較複雜的測試中,我們可以藉助XCTContext類來幫我們把測試邏輯分割成多個小的測試模組。比如說我們有一個業務,關聯多個模組,這個時候我們可以用類似下面的程式碼來處理:

// 模組 1
[XCTContext runActivityNamed:@"step1" block:^(id<XCTActivity>  _Nonnull activity) {
    XCTestExpectation *expect1 = [self expectationWithDescription:@"asyncTest1"];

    [TTFakeNetworkingInstance requestWithService:apiRecordSave completionHandler:^(NSDictionary *response) {
        XCTAssertTrue([response[@"code"] isEqualToString:@"200"]);
        [expect1 fulfill];
    }];
    
}];

// 模組 2
[XCTContext runActivityNamed:@"step2" block:^(id<XCTActivity>  _Nonnull activity) {
    XCTestExpectation *expect2 = [self expectationWithDescription:@"asyncTest2"];

    [TTFakeNetworkingInstance requestWithService:apiRecordDelete completionHandler:^(NSDictionary *response) {
        XCTAssertTrue([response[@"code"] isEqualToString:@"200"]);
        [expect2 fulfill];
    }];
    
}];

[self waitShortTimeForExpectations];
複製程式碼

如果測試成功,可以在 Report 導航欄看到成功資訊,它會按照你設定的模組分別展示測試結果。

iOS 單元測試和 UI 測試快速入門

如果測試失敗,你可以看到哪些模組是成功的,和在哪些模組中失敗了。

iOS 單元測試和 UI 測試快速入門

除此之外,你還可以嘗試多層巢狀,activity 裡面巢狀 activity。

3.3 截圖

在 UI 測試中有 2 種型別支援通過程式碼截圖,分別是XCUIElementXCUIScreen

// 獲取一個截圖物件
XCUIScreenshot *screenshot = [app screenshot];

// 例項化一個附件物件 並傳入截圖物件
XCTAttachment *attachment = [XCTAttachment attachmentWithScreenshot:screenshot];

// 附件的儲存策略 如果選擇 XCTAttachmentLifetimeDeleteOnSuccess 則測試成功的情況會被刪除
attachment.lifetime = XCTAttachmentLifetimeKeepAlways;

// 設定一個名字 方便區分
attachment.name = @"MyScreenshot";

[self addAttachment:attachment];
複製程式碼

在測試結束後,可以在 Report 導航欄中檢視截圖:

iOS 單元測試和 UI 測試快速入門

除此之外 Xcode 提供了自動截圖的功能,可以幫助我們在每一個互動操作之後自動截圖。此功能會產生大量截圖,需要謹慎使用,一般情況最好勾選Delete when each test succeeds,需要在 Edit Scheme -> Test -> Options 中開啟。

iOS 單元測試和 UI 測試快速入門

所以你可以根據你的需求選擇適當的截圖策略。

3.4 程式碼覆蓋率

程式碼覆蓋率在 Report 導航欄中檢視,它除了可以幫你統計測試用例覆蓋的程式碼百分比,還可以幫助你發現哪些程式碼是沒有被測試用例覆蓋的,需要在 Edit Scheme -> Test -> Options 中開啟。

iOS 單元測試和 UI 測試快速入門

你還可以選擇統計哪些 targets 的程式碼覆蓋率,all targets表示統計專案內所有 targets 的覆蓋率,some targets需要你手動新增 target ,只統計手動新增的 target 的覆蓋率。

iOS 單元測試和 UI 測試快速入門

除了在 Report 導航欄中檢視程式碼覆蓋率的方式,你還可以藉助蘋果提供的命令列工具xccov來生成程式碼覆蓋率報告。值得一提的是,xccov還能輸出 JSON 格式的報告。

3.5 跳過部分測試

在 Xcode 10 中新增功能,在 Edit Scheme -> Test -> Info -> Tests 中可以通過取消勾選,來選擇跳過部分測試用例。在 target 的 Options 選項中,Automatically includes new tests,選項是預設勾選的,新建的測試檔案會自動新增進去。

iOS 單元測試和 UI 測試快速入門

3.6 測試用例的執行順序

預設情況下,測試用例執行的順序是按字母順序來執行的,按固定順序執行可能會使一些隱式的依賴關係無法被發現。現在有了隨機的執行順序,就可以挖掘出那些隱式的依賴關係。可以在 Edit Scheme -> Test -> Info -> Tests -> Options 中開啟該功能。

iOS 單元測試和 UI 測試快速入門

3.7 並行測試

並行測試可以同時進行多個測試,從而節省大量時間。在測試時會啟動多個模擬器,模擬器之間的資料都是隔離的,可以在 Edit Scheme -> Test -> Info -> Tests -> Options 中開啟該功能。

iOS 單元測試和 UI 測試快速入門

對於並行測試的一些建議:

  • 某個測試用例需要消耗大量時間的類,可以拆分成多個類並行測試,從而節省時間。
  • 你需要清楚哪些測試在並行執行時是不安全的,避免並行執行這些測試。
  • 效能測試的可以統一放在一個 Bundle 中,禁用並行執行。

四、工程可測性

單元測試的結構:

  1. 準備輸入
  2. 執行需要測試的程式碼
  3. 驗證輸出

可測試程式碼的特徵:

  • 避免過多輸入
  • 輸出是可見的
  • 沒有隱藏的狀態

4.1 可測性 Tips

  • 引數化:減少共享單例的引用,測試的方法需要接受引數輸入和明確的輸出。
  • 分離邏輯和結果:抽離邏輯,最終測試程式碼儘量精簡。
  • 平衡單元測試和 UI 測試:單元測試適合測試使用者互動行為無法覆蓋的程式碼,和小而完整的程式碼。UI 測試更適合測試大範圍的功能集合。

4.2 幫助 UI 測試更加完善

  • 用精巧聰明的程式碼縮減 UI 測試的程式碼量
  • 將複雜的查詢邏輯進行封裝
  • 對常見組合操作的 UI 測試流程進行封裝
  • 避免邏輯混亂和冗餘

4.3 合理使用快捷鍵

  • 避免使用 macOS 的選單欄
  • 使邏輯上下緊密,避免邏輯分離

4.3 測試程式碼的質量

  • 寫測試程式碼之前多構思很重要
  • 測試程式碼應該支撐你的 App 的擴充
  • 業務程式碼中的編碼原則也適用於測試程式碼

本節內容根據 WWDC 2017 Session 414:Engineering for Testability 粗略總結得出,如果需要了解更多相關內容可以檢視相關視訊。

總結

掌握這些測試相關 API 並不難,但是好的程式碼需要經過完整專案的磨礪和時間的考驗。同時也可以借鑑一些開源專案的測試程式碼,嘗試著爬上巨人的肩膀。

參考

WWDC 2018 Session 403:What's New in Testing

WWDC 2017 Session 414:Engineering for Testability

WWDC 2017 Session 409:What's New in Testing

WWDC 2015 Session 406:UI Testing in Xcode

WWDC 2014 Session 414:Testing in Xcode 6

相關文章