翻譯:iOS Swift單元測試 從入門到精通 Unit Test和UI測試 UITest

架構師易筋發表於2020-10-13

說明

編寫測試並不是魅力十足,但是由於測試可以防止您閃亮的應用程式變成臭蟲纏身的垃圾,所以這是必要的。如果您正在閱讀本教程,您已經知道應該為程式碼和UI編寫測試,但是可能不知道如何做。

您可能有一個正在執行的應用程式,但是您想測試為擴充套件該應用程式所做的更改。也許您已經編寫了測試,但是不確定它們是否是正確的測試。或者,您已經開始開發新的應用程式,並想隨身進行測試。

本教程將向您展示:

  • 如何使用Xcode的Test導航器來測試應用程式的模型和非同步方法
  • 如何使用存根和模擬來偽造與庫或系統物件的互動
  • 如何測試UI和效能
  • 如何使用程式碼覆蓋率工具

在此過程中,您將掌握測試忍者所使用的一些詞彙。

找出要測試的內容

在編寫任何測試之前,瞭解基礎知識很重要。您需要測試什麼?

如果您的目標是擴充套件現有應用程式,則應首先為計劃更改的任何元件編寫測試。

通常,測試應涵蓋:

  • 核心功能:模型類和方法及其與控制器的互動
  • 最常見的UI工作流程
  • 邊界條件
  • Bug修復

測試最佳實踐

首字母縮寫詞FIRST描述了有效單元測試的一組簡明標準。這些標準是:

  • Fast快速:測試應該快速進行。
  • Independent/Isolated獨立/隔離:測試不應相互共享狀態。
  • Repeatable可重複:每次執行測試時,您都應獲得相同的結果。外部資料提供者或併發問題可能會導致間歇性故障。
  • Self-validating自驗證:測試應完全自動化。輸出應該是“通過”或“失敗”,而不是依賴於程式設計師對日誌檔案的解釋。
  • Timely及時:理想情況下,應該在編寫要測試的生產程式碼之前編寫測試(測試驅動開發)。

遵循FIRST原則將使您的測試清晰且有用,而不是成為您應用程式的障礙。

程式碼下載

程式碼下載

有兩個單獨的入門專案:BullsEye和HalfTunes。

  • BullsEye基於iOS Apprentice中的示例應用程式。遊戲邏輯在BullsEyeGame該類中,您將在本教程中進行測試。
  • HalfTunes是URLSession教程中示例應用程式的更新版本。使用者可以在iTunes API中查詢歌曲,然後下載並播放歌曲片段。

Xcode中的單元測試

該測試導航提供與測試工作的最簡單的方法; 您將使用它來建立測試目標並針對您的應用執行測試。

建立單元測試目標

開啟BullsEye專案,然後按Command-6開啟Test導航器

單擊左下角的+按鈕,然後從選單中選擇New Unit Test Target…
在這裡插入圖片描述
接受預設名稱BullsEyeTests。當測試包出現在“測試”導航器中時,單擊以在編輯器中開啟該包。如果捆綁軟體未自動顯示,請通過單擊其他導航器之一進行故障排除,然後返回到“測試”導航器。
在這裡插入圖片描述
預設的模板匯入測試框架,XCTest,並定義一個BullsEyeTests子類XCTestCase,用setUpWithError()tearDownWithError()以及例如測試方法。

有三種執行測試的方法:

  1. ProductTestCommand-U。這兩個都執行所有測試類。
  2. 單擊“測試”導航器中的箭頭按鈕。
  3. 單擊裝訂線中的菱形按鈕。
    在這裡插入圖片描述
    您也可以通過單擊“測試”導航器或裝訂線中的菱形,執行單個測試方法。

嘗試不同的方式執行測試,以瞭解所需的時間和外觀。樣本測試尚未執行任何操作,因此它們執行得非常快!

當所有測試成功時,菱形將變為綠色並顯示對勾。您可以單擊末尾的灰色菱形testPerformanceExample()以開啟效能結果:
在這裡插入圖片描述
您不需要testPerformanceExample()或不需要testExample()本教程,請刪除它們。

使用XCTAssert測試模型

首先,您將使用XCTAssert函式來測試BullsEye模型的核心功能:BullsEyeGame物件是否正確計算出回合的分數?

BullsEyeTests.swift中,在import語句下面新增以下行:

@testable import BullsEye

這使單元測試可以訪問BullsEye中的內部型別和函式。

BullsEyeTests類的頂部,新增以下屬性:

var sut: BullsEyeGame!

這將為佔位符建立佔位符,佔位符BullsEyeGame是被測系統System Under Test(SUT)或此測試用例類與測試有關的物件。

接下來,將其內容替換為setUpWithError()

sut = BullsEyeGame()
sut.startNewGame()

這將BullsEyeGame在類級別建立一個物件,因此該測試類中的所有測試都可以訪問SUT物件的屬性和方法。

在這裡,您還呼叫了遊戲的startNewGame(),它會初始化targetValue。許多測試將targetValue用來測試遊戲是否正確計算了分數。

在忘記之前,請在中釋放SUT物件tearDownWithError()。將其內容替換為:

sut = nil

注意:優良作法是在其中建立SUT setUpWithError()並將其釋放,tearDownWithError()以確保每次測試均以乾淨的狀態開始。有關更多討論,請檢視Jon Reid關於該主題的帖子

編寫您的第一個測試

現在,您準備編寫第一個測試!

將以下程式碼新增到的末尾BullsEyeTests

func testScoreIsComputed() {
  // 1. given
  let guess = sut.targetValue + 5

  // 2. when
  sut.check(guess: guess)

  // 3. then
  XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}

測試方法的名稱始終以test開頭,然後是對其進行測試的描述。

良好的做法是將測試格式化為給定的,時間和隨後的部分:

  1. 給定:在這裡,您可以設定所需的任何值。在此示例中,您建立了一個guess值,以便可以指定與的不同之處targetValue。
  2. 時間:在本部分中,您將執行被測試的程式碼:呼叫check(guess:)。
  3. 然後:這是您將在此部分中宣告期望結果的部分,並在測試失敗時顯示一條訊息。在這種情況下,sut.scoreRound應等於95(100 – 5)。

單擊裝訂線或“測試”導航器中的菱形圖示,執行測試。這將構建並執行該應用程式,菱形圖示將變為綠色的選中標記!
在這裡插入圖片描述
注意:要檢視XCTestAssertions的完整列表,請轉到Apple的“按類別列出的斷言”。

除錯測試

有一個BullsEyeGame故意內建的錯誤,您現在就練習找到它。要檢視執行中的錯誤,您將建立一個測試,該測試在給定的部分中減去5 ,並使其他所有內容保持不變。targetValue

新增以下測試:

func testScoreIsComputedWhenGuessLTTarget() {
  // 1. given
  let guess = sut.targetValue - 5

  // 2. when
  sut.check(guess: guess)

  // 3. then
  XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}

guess和之間的差targetValue仍為5,因此分數仍應為95。

Breakpoint導航器中,新增Test Failure Breakpoint。當測試方法釋出故障斷言時,這將停止測試運​​行。
在這裡插入圖片描述
執行您的測試,它應該在XCTAssertEqual測試失敗的情況下停止。

檢查sutguess在除錯控制檯中:
在這裡插入圖片描述
guesstargetValue - 5但是scoreRound105,而不是95

為了進一步研究,使用正常的除錯過程:設定一個斷點的時候發言,並在一個BullsEyeGame.swift,裡面check(guess:),在那裡建立difference。然後再次執行測試,然後越過let difference語句檢查difference應用程式中的值:
在這裡插入圖片描述
問題是那difference是負數,所以分數是100 –(-5)。為了解決這個問題,你應該使用絕對值的difference。在中check(guess:),取消註釋正確的行並刪除不正確的行。

刪除兩個斷點,然後再次執行測試以確認現在可以成功了。

使用XCTestExpectation測試非同步操作

既然您已經學會了如何測試模型和除錯測試失敗,那麼現在該繼續測試非同步程式碼了。

開啟HalfTunes專案。它用於URLSession查詢iTunes API和下載歌曲樣本。假設您想對其進行修改以使用AlamoFire進行網路操作。要檢視是否有任何中斷,您應該為網路操作編寫測試,並在更改程式碼之前和之後執行它們。

URLSession方法是非同步的:它們立即返回,但是要等到以後再執行。要測試非同步方法,您XCTestExpectation可以使測試等待非同步操作完成。

非同步測試通常很慢,因此應將它們與更快的單元測試分開。

建立一個名為HalfTunesSlowTests的新單元測試目標。開啟HalfTunesSlowTests該類,然後在現有import語句的下面匯入HalfTunes應用模組:

@testable import HalfTunes

此類中的所有測試都使用預設值URLSession將請求傳送到Apple的伺服器,因此宣告一個sut物件,在中建立它,setUpWithError()然後在中釋放它tearDownWithError()

HalfTunesSlowTests類的內容替換為:

var sut: URLSession!

override func setUpWithError() throws{
  sut = URLSession(configuration: .default)
}

override func tearDownWithError() throws{
  sut = nil
}

接下來,新增此非同步測試:

// Asynchronous test: success fast, failure slow
func testValidCallToiTunesGetsHTTPStatusCode200() {
  // given
  let url = 
    URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
  // 1
  let promise = expectation(description: "Status code: 200")

  // when
  let dataTask = sut.dataTask(with: url!) { data, response, error in
    // then
    if let error = error {
      XCTFail("Error: \(error.localizedDescription)")
      return
    } else if let statusCode = (response as? HTTPURLResponse)?.statusCode {
      if statusCode == 200 {
        // 2
        promise.fulfill()
      } else {
        XCTFail("Status code: \(statusCode)")
      }
    }
  }
  dataTask.resume()
  // 3
  wait(for: [promise], timeout: 5)
}

此測試檢查向iTunes傳送有效查詢是否返回200狀態碼。大多數程式碼與您在應用程式中編寫的程式碼相同,但有以下幾行:

  1. Expectation(description :):返回XCTestExpectation儲存在中的物件promise。該description引數描述了您期望發生的事情。
  2. promise.fulfill():在非同步方法的完成處理程式的成功條件關閉中呼叫此函式,以標記已滿足期望。
  3. wait(for:timeout :):保持測試執行,直到滿足所有期望或timeout間隔結束(以先發生者為準)。
    執行測試。如果您已連線到網際網路,則在將應用程式載入到模擬器中後,測試應該需要大約一秒鐘才能成功。

快速失敗

失敗很痛苦,但這並不一定要花很長時間。

要體驗失敗,只需從URL中的“ itunes”中刪除“ s”:

let url = 
  URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")

執行測試。它失敗,但是需要整個超時間隔!這是因為您假設請求將始終成功,所以您就在那裡promise.fulfill()。由於請求失敗,因此僅在超時到期時才完成。

您可以通過更改以下假設來改進此方法,並使測試更快失敗:不要等待請求成功,而要等到非同步方法的完成處理程式被呼叫。一旦應用程式從伺服器接收到滿足預期的響應(“確定”或“錯誤”),就會發生這種情況。然後,您的測試可以檢查請求是否成功。

要檢視其工作原理,請建立一個新測試。

但首先,請通過撤消對所做的更改來修復以前的測試url
然後,將以下測試新增到您的類:

func testCallToiTunesCompletes() {
  // given
  let url = 
    URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")
  let promise = expectation(description: "Completion handler invoked")
  var statusCode: Int?
  var responseError: Error?

  // when
  let dataTask = sut.dataTask(with: url!) { data, response, error in
    statusCode = (response as? HTTPURLResponse)?.statusCode
    responseError = error
    promise.fulfill()
  }
  dataTask.resume()
  wait(for: [promise], timeout: 5)

  // then
  XCTAssertNil(responseError)
  XCTAssertEqual(statusCode, 200)
}

關鍵區別在於,只需輸入完成處理程式即可滿足期望,而這僅需一秒鐘即可完成。如果請求失敗,則then斷言失敗。

執行測試。現在大約需要一秒鐘才能失敗。它失敗是因為請求失敗,而不是因為測試執行超出了timeout

修復url,然後再次執行測試以確認現在可以成功進行。

偽造物件和互動

非同步測試使您有信心程式碼可以為非同步API生成正確的輸入。您可能還需要測試,當程式碼從接收到輸入時,程式碼是否可以正常工作URLSession,或者它可以正確更新使用者的預設資料庫或iCloud容器。

大多數應用程式都與系統或庫物件(您無法控制的物件)進行互動,並且與這些物件進行互動的測試可能很慢且不可重複,這違反了FIRST的兩項原則。相反,您可以通過從存根獲取輸入或通過更新模擬物件來偽造互動。

當您的程式碼對系統或庫物件有依賴性時,請進行偽造。您可以通過建立一個假的物件來演那個角色,做這個注射這個假入你的程式碼。喬恩·裡德(Jon Reid)進行的依賴注入描述了幾種方法

來自Stub的假輸入

在此測試中,您將通過檢查應用程式updateSearchResults(_:)是否正確分析了會話下載的資料searchResults.count是否正確。SUT是檢視控制器,您將使用存根和一些預下載的資料來偽造會話。

轉到“測試”導航器並新增一個新的“單元測試目標”。將其命名為HalfTunesFakeTests。開啟HalfTunesFakeTests.swift並在import語句下面匯入HalfTunes應用模組:

@testable import HalfTunes

現在,用以下內容替換HalfTunesFakeTests類的內容:

var sut: SearchViewController!

override func setUpWithError() throws{
  sut = UIStoryboard(name: "Main", bundle: nil)
    .instantiateInitialViewController() as? SearchViewController
}

override func tearDownWithError() throws{
  sut = nil
}

這宣告為SUT,SearchViewController在中建立它並在setUpWithError()其中釋放它tearDownWithError()

注意:SUT是檢視控制器,因為HalfTunes有一個很大的檢視控制器問題-所有工作都在SearchViewController.swift中完成。將網路程式碼移動到單獨的模組中將減少此問題,並使測試更加容易。

接下來,您將需要一些示例JSON資料,您的虛假會話將這些資料提供給測試。僅需執行以下幾項操作,因此為了限制您在iTunes中的下載結果,請附加&limit=3到URL字串:

https://itunes.apple.com/search?media=music&entity=song&term=abba&limit=3

複製此URL並將其貼上到瀏覽器中。這將下載一個名為1.txt1.txt.js或類似檔案。預覽它以確認它是一個JSON檔案,然後將其重新命名為abbaData.json

現在,返回Xcode並轉到Project導航器。將檔案新增到HalfTunesFakeTests組。

HalfTunes專案包含支援檔案DHURLSessionMock.swift。這定義了一個簡單的協議,名為DHURLSession,帶有方法(存根)以使用aURLa建立資料任務URLRequest。它還定義了URLSessionMock,它使用初始化程式符合此協議,該初始化程式使URLSession您可以根據資料,響應和錯誤的選擇建立模擬物件。

要設定偽造品,請轉到HalfTunesFakeTests.swift並將以下內容新增setUp()到建立SUT的語句之後:

let testBundle = Bundle(for: type(of: self))
let path = testBundle.path(forResource: "abbaData", ofType: "json")
let data = try? Data(contentsOf: URL(fileURLWithPath: path!), options: .alwaysMapped)

let url = 
  URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
let urlResponse = HTTPURLResponse(
  url: url!, 
  statusCode: 200, 
  httpVersion: nil, 
  headerFields: nil)

let sessionMock = URLSessionMock(data: data, response: urlResponse, error: nil)
sut.defaultSession = sessionMock

這將設定假資料和響應並建立假會話物件。最後,最後,它將偽造的會話作為的屬性注入到應用程式中sut。

現在,您可以編寫測試來檢查呼叫是否updateSearchResults(_:)解析偽資料了。新增以下測試:

func test_UpdateSearchResults_ParsesData() {
  // given
  let promise = expectation(description: "Status code: 200")

  // when
  XCTAssertEqual(
    sut.searchResults.count, 
    0, 
    "searchResults should be empty before the data task runs")
  let url = 
    URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
  let dataTask = sut.defaultSession.dataTask(with: url!) {
    data, response, error in
    // if HTTP request is successful, call updateSearchResults(_:) 
    // which parses the response data into Tracks
    if let error = error {
      print(error.localizedDescription)
    } else if let httpResponse = response as? HTTPURLResponse,
      httpResponse.statusCode == 200 {
      self.sut.updateSearchResults(data)
    }
    promise.fulfill()
  }
  dataTask.resume()
  wait(for: [promise], timeout: 5)

  // then
  XCTAssertEqual(sut.searchResults.count, 3, "Didn't parse 3 items from fake response")
}

您仍然必須將其編寫為非同步測試,因為存根假裝是非同步方法。

該時的說法是,searchResults是資料任務執行前空。這應該是正確的,因為您在中建立了一個全新的SUT setUp()

假資料包含三個JSONTrack物件,所以然後斷言是檢視控制器的searchResults陣列包含三個專案。

執行測試。它應該很快就能成功,因為沒有任何真正的網路連線!

假更新到模擬物件

先前的測試使用存根提供來自假物件的輸入。接下來,您將使用模擬物件來測試程式碼是否正確更新UserDefaults

重新開啟BullsEye專案。該應用程式具有兩種遊戲風格:使用者要麼移動滑塊以匹配目標值,要麼從滑塊位置猜測目標值。右下角的分段控制元件可切換遊戲樣式並將其儲存在使用者預設設定中。

您的下一個測試將檢查應用程式是否正確儲存了gameStyle屬性。

在“測試”導航器中,單擊“新建單元測試類”,並將其命名為BullsEyeMockTests。在import語句下面新增以下內容:

@testable import BullsEye

class MockUserDefaults: UserDefaults {
  var gameStyleChanged = 0
  override func set(_ value: Int, forKey defaultName: String) {
    if defaultName == "gameStyle" {
      gameStyleChanged += 1
    }
  }
}

MockUserDefaults重寫set(_:forKey:)以增加gameStyleChanged標誌。通常,您會看到類似的測試會設定一個Bool變數,但是增加Int會給您帶來更大的靈活性-例如,您的測試可以檢查該方法僅被呼叫一次。

在中宣告SUT和模擬物件BullsEyeMockTests

var sut: ViewController!
var mockUserDefaults: MockUserDefaults!

接下來,替換預設setUpWithError()tearDownWithError()與此:

override func setUpWithError() throws {

  sut = UIStoryboard(name: "Main", bundle: nil)
    .instantiateInitialViewController() as? ViewController
  mockUserDefaults = MockUserDefaults(suiteName: "testing")
  sut.defaults = mockUserDefaults
}

override func tearDownWithError() throws {
  sut = nil
  mockUserDefaults = nil
}

這將建立SUT和模擬物件,並將模擬物件作為SUT的屬性注入。

現在,將模板中的兩個預設測試方法替換為:

func testGameStyleCanBeChanged() {
  // given
  let segmentedControl = UISegmentedControl()

  // when
  XCTAssertEqual(
    mockUserDefaults.gameStyleChanged, 
    0, 
    "gameStyleChanged should be 0 before sendActions")
  segmentedControl.addTarget(sut,
    action: #selector(ViewController.chooseGameStyle(_:)), for: .valueChanged)
  segmentedControl.sendActions(for: .valueChanged)

  // then
  XCTAssertEqual(
    mockUserDefaults.gameStyleChanged, 
    1, 
    "gameStyle user default wasn't changed")
}

在當斷言是該gameStyleChanged標誌為0的測試方法改變分段控制之前。因此,如果then斷言也成立,則意味著只set(_:forKey:)被呼叫了一次。

執行測試;它應該成功。

Xcode中的UI測試

UI測試使您可以測試與使用者介面的互動。使用者介面測試的工作原理是通過查詢查詢應用程式的使用者介面物件,綜合事件,然後將事件傳送到這些物件。API使您可以檢查UI物件的屬性和狀態,以便將它們與預期狀態進行比較。

BullsEye專案的Test導航器中,新增一個新的UI Test Target。檢查要測試的目標是BullsEye,然後接受預設名稱BullsEyeUITests

開啟BullsEyeUITests.swift並將此屬性新增到BullsEyeUITests類的頂部:

var app: XCUIApplication!

在中setUpWithError(),將語句替換XCUIApplication().launch()為以下內容:

app = XCUIApplication()
app.launch()

將名稱更改testExample()testGameStyleSwitch()

在其中開啟新行,testGameStyleSwitch()然後單擊編輯器視窗底部的紅色“記錄”按鈕:
在這裡插入圖片描述
這會以將您的互動記錄為測試命令的模式在模擬器中開啟該應用。應用載入後,點選遊戲樣式開關的“滑動”部分和頂部標籤。然後,單擊“ Xcode記錄”按鈕以停止記錄。

現在,您在以下三行中testGameStyleSwitch()

let app = XCUIApplication()
app.buttons["Slide"].tap()
app.staticTexts["Get as close as you can to: "].tap()

記錄器已建立程式碼以測試您在應用程式中測試的相同操作。將水龍頭髮送到滑塊和標籤。您將以這些為基礎來建立自己的UI測試。
如果您看到其他任何語句,只需刪除它們即可。

第一行與您在中建立的屬性重複setUpWithError(),因此請刪除該行。您無需點選任何內容,因此也請.tap()在第2行和第3行的末尾刪除。現在,開啟旁邊的小選單["Slide"]並選擇segmentedControls.buttons["Slide"]

您剩下的應該是以下內容:

app.segmentedControls.buttons["Slide"]
app.staticTexts["Get as close as you can to: "]

點選其他任何物件,讓記錄儀幫助您找到可以在測試中訪問的程式碼。現在,用以下程式碼替換這些行以建立給定的部分:

// given
let slideButton = app.segmentedControls.buttons["Slide"]
let typeButton = app.segmentedControls.buttons["Type"]
let slideLabel = app.staticTexts["Get as close as you can to: "]
let typeLabel = app.staticTexts["Guess where the slider is: "]

現在您已經有了分段控制元件中兩個按鈕的名稱以及兩個可能的頂部標籤,在下面新增以下程式碼:

// then
if slideButton.isSelected {
  XCTAssertTrue(slideLabel.exists)
  XCTAssertFalse(typeLabel.exists)

  typeButton.tap()
  XCTAssertTrue(typeLabel.exists)
  XCTAssertFalse(slideLabel.exists)
} else if typeButton.isSelected {
  XCTAssertTrue(typeLabel.exists)
  XCTAssertFalse(slideLabel.exists)

  slideButton.tap()
  XCTAssertTrue(slideLabel.exists)
  XCTAssertFalse(typeLabel.exists)
}

這將檢查您tap()在分段控制元件中的每個按鈕上是否存在正確的標籤。執行測試-所有斷言都應成功。

效能測試

根據Apple的文件:效能測試採用您要評估的程式碼塊並將其執行十次,以收集執行的平均執行時間和標準差。這些單獨測量值的平均值形成測試執行的值,然後可以將其與基準進行比較以評估成功或失敗。

編寫效能測試非常簡單:您只需將要測量的程式碼放入的閉包中measure()

要檢視實際效果,請重新開啟HalfTunes專案,並在HalfTunesFakeTests.swift中新增以下測試:

func test_StartDownload_Performance() {
  let track = Track(
    name: "Waterloo", 
    artist: "ABBA",
    previewUrl: 
      "http://a821.phobos.apple.com/us/r30/Music/d7/ba/ce/mzm.vsyjlsff.aac.p.m4a")

  measure {
    self.sut.startDownload(track)
  }
}

執行測試,然後單擊measure()尾隨閉合符開頭旁邊的圖示以檢視統計資訊。
在這裡插入圖片描述
單擊設定基準以設定參考時間。然後,再次執行效能測試並檢視結果-它可能比基準更好或更差。使用“編輯”按鈕可以將基準重置為該新結果。

基準是按裝置配置儲存的,因此您可以在多個不同的裝置上執行相同的測試,並根據特定配置的處理器速度,記憶體等使每個基準保持不同的基準。

每當您對應用程式進行更改而可能影響所測試方法的效能時,請再次執行效能測試以檢視其與基準的比較情況。

程式碼覆蓋率

程式碼覆蓋率工具會告訴您測試實際上正在執行哪些應用程式程式碼,因此您知道該應用程式程式碼的哪些部分尚未(尚未)測試。

要啟用程式碼覆蓋率,請編輯方案的“測試”操作並選中“選項”選項卡下的“收集覆蓋率”核取方塊:
在這裡插入圖片描述
執行所有測試(Command-U),然後開啟報告導航器(Command-9)。選擇該列表頂部專案下的Coverage
在這裡插入圖片描述
單擊顯示三角形以檢視SearchViewController.swift中的函式和閉包列表:

在這裡插入圖片描述
向下滾動updateSearchResults(_:)以檢視覆蓋率為87.9%。

單擊此功能的箭頭按鈕以開啟該功能的原始檔。當您將滑鼠懸停在右側欄中的coverage註釋上時,程式碼部分將突出顯示綠色或紅色:
在這裡插入圖片描述
覆蓋註釋顯示測試擊中每個程式碼段的次數。未呼叫的部分以紅色突出顯示。如您所料,for迴圈執行了3次,但錯誤路徑中的任何內容均未執行。

要增加此功能的覆蓋範圍,可以複製abbaData.json,然後對其進行編輯,以免引起不同的錯誤。例如,更改"results""result"一個測試命中print("Results key not found in dictionary")

100%覆蓋率?

您應該努力爭取100%的程式碼覆蓋率嗎?Google的“ 100%單元測試覆蓋率”,您會發現一系列支援和反對的論點,以及關於“ 100%覆蓋率”這一定義的爭論。反對它的人說最後的10-15%不值得付出努力。有關它的說法,最後10%至15%是最重要的,因為它很難測試。谷歌“很難對不良設計進行單元測試”,以找到令人信服的論點,即無法測試的程式碼是更深層的設計問題的跡象

然後去哪兒?

現在,您有了一些很棒的工具可用於為專案編寫測試。我希望這個iOS單元測試和UI測試教程能夠給您信心,可以測試所有東西!

您可以使用本教程頂部或底部的“下載材料”按鈕下載專案的完整版本。通過新增自己的其他測試來繼續發展技能。

以下是一些需要進一步研究的資源:

參考

https://www.raywenderlich.com/960290-ios-unit-testing-and-ui-testing-tutorial

相關文章