前言
平時寫完業務程式碼的時候都會去自己測試一遍,後面每次有修改都需要重複測,不管是一個業務流程還是一個工具類,其實都可以通過測試框架來幫助我們完成測試,特別是一些頻繁修改的程式碼,更需要嚴謹的測試。在淺淺地對自動化測試有一些瞭解時,覺得寫測試程式碼挺耗時間,但其實對後期的幫助是非常大的,可以根據自己的實際情況來決定哪些地方需要加入自動化測試。
本文內容適合剛接觸 iOS 自動化測試的同學,基本內容來自於各年 WWDC 的多個 Sessions,本文程式碼部分基於我的一個學習 Demo,喜歡的可以瞭解一下。本文介紹的大致內容包括:
- 單元測試
- UI 測試
- 擴充 Tips
- 工程可測性
一、單元測試
1.1 加入測試 Target
在新建專案時,勾選Include Unit Tests
和Include 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 可以測試單個用例,也可以測試全部用例。
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
。
我們可以通過點選measureBlock:
方法左邊菱形圓心 icon ,來設定Baseline
,設定之後需要點選save
儲存。之後再執行測試用例時,如果成功,左邊的icon會從圓心變成一個 ✅。
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
- 測試覆蓋率
1.6 進行單元測試
我新建一個時間工具類,幫助我轉換時間,在使用之前,我們需要先進行測試,以保證功能完整且正確。
這個工具類有以下 4 個公共方法,
@interface TTDateFormatter : NSDate+ (NSString *)stringFormatWithDate:(NSDate *)date;
+ (NSDate *)dateFormatWithString:(NSString *)dateString;
+ (BOOL)isTodayWithDateString:(NSString *)dateString;
+ (NSString *)getHowLongAgoWithTimeStamp:(NSTimeInterval)timeStamp;
@end複製程式碼
針對一個工具類的測試我們可以新建一個TTDateFormatterTests
測試類,繼承一個測試基類。再根據不同的方法寫不同的測試方法。如果有if
和switch
等條件語句導致邏輯分支的程式碼,儘量使各個邏輯分支都能測試到,可以配合程式碼覆蓋率來檢查哪些邏輯分支未測試。
@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 測試類,你能再下方看到一個小紅點,點選小紅點開始錄製你的互動。
在你進行互動時,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 一欄填入 AddXCUIElement *addButton = app.buttons[@"add"];
// 模擬使用者點選按鈕[addButton tap];
複製程式碼
通過型別加 identifier 的方式來定位的控制元件元素的方式,可以滿足大多數場景。
2.2.3 XCUIElementQuery
XCUIElementQuery
是一個用來定位控制元件元素的類,一般是一組符合篩選條件的元素集合。如app.buttons
即返回 XCUIElementQuery 例項,是包含了當前所有的button
的集合,你可以再通過 XCUIElementQuery
的方法做下一步的篩選。
XCUIElementQuery 常見定位元素的方法:
-
count:匹配的數量;
// 當 navigationBars 的 count 等於 1 時,你可以直接定位到 navigationBarapp.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()// 啟動 appapp.launch()// 定位元素let addButton = app.buttons[“Add”]// 模擬使用者互動事件addButton.tap()// 驗證測試是否通過XCTTAssertionEqual(app.tables.cells.cout, 1)複製程式碼
大多數 UI 測試都是基於使用者行為驅動,根據設計好的使用者的操作流程,測試整個流程的結果。我設計了一個簡單的筆記,主要有 3 步操作,分別是建立筆記、展示筆記和刪除筆記,下面一起來看看如何進行測試。
// 測試主流程- (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.cells
的count
屬性,當滿足NSPredicate
條件時,expectation
相當於自動fullfill
。如果一直不滿足條件,會一直等待直至超時,除此之外還可以用通知和 KVO 的方式實現。
測試過程:
三、 擴充 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 導航欄看到成功資訊,它會按照你設定的模組分別展示測試結果。
如果測試失敗,你可以看到哪些模組是成功的,和在哪些模組中失敗了。
除此之外,你還可以嘗試多層巢狀,activity 裡面巢狀 activity。
3.3 截圖
在 UI 測試中有 2 種型別支援通過程式碼截圖,分別是XCUIElement
和XCUIScreen
。
// 獲取一個截圖物件XCUIScreenshot *screenshot = [app screenshot];
// 例項化一個附件物件 並傳入截圖物件XCTAttachment *attachment = [XCTAttachment attachmentWithScreenshot:screenshot];
// 附件的儲存策略 如果選擇 XCTAttachmentLifetimeDeleteOnSuccess 則測試成功的情況會被刪除attachment.lifetime = XCTAttachmentLifetimeKeepAlways;
// 設定一個名字 方便區分attachment.name = @"MyScreenshot";
[self addAttachment:attachment];
複製程式碼
在測試結束後,可以在 Report 導航欄中檢視截圖:
除此之外 Xcode 提供了自動截圖的功能,可以幫助我們在每一個互動操作之後自動截圖。此功能會產生大量截圖,需要謹慎使用,一般情況最好勾選Delete when each test succeeds
,需要在 Edit Scheme ->
Test ->
Options 中開啟。
所以你可以根據你的需求選擇適當的截圖策略。
3.4 程式碼覆蓋率
程式碼覆蓋率在 Report 導航欄中檢視,它除了可以幫你統計測試用例覆蓋的程式碼百分比,還可以幫助你發現哪些程式碼是沒有被測試用例覆蓋的,需要在 Edit Scheme ->
Test ->
Options 中開啟。
你還可以選擇統計哪些 targets 的程式碼覆蓋率,all targets
表示統計專案內所有 targets 的覆蓋率,some targets
需要你手動新增 target ,只統計手動新增的 target 的覆蓋率。
除了在 Report 導航欄中檢視程式碼覆蓋率的方式,你還可以藉助蘋果提供的命令列工具xccov
來生成程式碼覆蓋率報告。值得一提的是,xccov
還能輸出 JSON 格式的報告。
3.5 跳過部分測試
在 Xcode 10 中新增功能,在 Edit Scheme ->
Test ->
Info ->
Tests 中可以通過取消勾選,來選擇跳過部分測試用例。在 target 的 Options 選項中,Automatically includes new tests
,選項是預設勾選的,新建的測試檔案會自動新增進去。
3.6 測試用例的執行順序
預設情況下,測試用例執行的順序是按字母順序來執行的,按固定順序執行可能會使一些隱式的依賴關係無法被發現。現在有了隨機的執行順序,就可以挖掘出那些隱式的依賴關係。可以在 Edit Scheme ->
Test ->
Info ->
Tests ->
Options 中開啟該功能。
3.7 並行測試
並行測試可以同時進行多個測試,從而節省大量時間。在測試時會啟動多個模擬器,模擬器之間的資料都是隔離的,可以在 Edit Scheme ->
Test ->
Info ->
Tests ->
Options 中開啟該功能。
對於並行測試的一些建議:
- 某個測試用例需要消耗大量時間的類,可以拆分成多個類並行測試,從而節省時間。
- 你需要清楚哪些測試在並行執行時是不安全的,避免並行執行這些測試。
- 效能測試的可以統一放在一個 Bundle 中,禁用並行執行。
四、工程可測性
單元測試的結構:
- 準備輸入
- 執行需要測試的程式碼
- 驗證輸出
可測試程式碼的特徵:
- 避免過多輸入
- 輸出是可見的
- 沒有隱藏的狀態
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