自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

避免 Swift 單元測(cè)試中的強(qiáng)制解析

開發(fā) 后端
強(qiáng)制解析(使用 !)是 Swift 語言中不可或缺的一個(gè)重要特點(diǎn)(特別是和 Objective-C 的接口混合使用時(shí))。它回避了一些其他問題,使得 Swift 語言變得更加優(yōu)秀。

[[421417]]

本文轉(zhuǎn)載自微信公眾號(hào)「網(wǎng)羅開發(fā)」,作者Rickey王小吉。轉(zhuǎn)載本文請(qǐng)聯(lián)系網(wǎng)羅開發(fā)公眾號(hào)。

前言

強(qiáng)制解析(使用 !)是 Swift 語言中不可或缺的一個(gè)重要特點(diǎn)(特別是和 Objective-C 的接口混合使用時(shí))。它回避了一些其他問題,使得 Swift 語言變得更加優(yōu)秀。比如 處理 Swift 中非可選的可選值類型[1] 這篇文章中,在項(xiàng)目邏輯需要時(shí)使用強(qiáng)制解析去處理可選類型,將導(dǎo)致一些離奇的情況和崩潰。

所以盡可能地避免使用強(qiáng)制解析,將有助于搭建更加穩(wěn)定的應(yīng)用,并且在發(fā)生錯(cuò)誤時(shí)提供更好的報(bào)錯(cuò)信息。那么如果是編寫測(cè)試時(shí),情況會(huì)怎么樣呢?安全地處理可選類型和未知類型需要大量的代碼,那么問題就在于我們是否愿意為編寫測(cè)試做所有的額外工作。這就是我們這周將要探討的問題,讓我們開始深入研究吧!

測(cè)試代碼 vs 產(chǎn)品代碼

當(dāng)編寫測(cè)試代碼時(shí),我們經(jīng)常明確區(qū)分測(cè)試代碼和產(chǎn)品代碼。盡管保持這兩部分代碼的分離十分重要(我們不希望意外地讓我們的模擬測(cè)試對(duì)象成為 App Store 上架的部分??),但就代碼質(zhì)量來說,沒有必要進(jìn)行明顯區(qū)分。

如果你思考一下的話,我們想要對(duì)移交給使用者的代碼進(jìn)行高標(biāo)準(zhǔn)的要求,原因是什么呢?

我們想要我們的 app 為使用者穩(wěn)定、流暢地運(yùn)行。

  • 我們想要我們的 app 在未來易于維護(hù)和修改。
  • 我們想要更容易讓新人融入我們的團(tuán)隊(duì)。
  • 現(xiàn)在如果反過來考慮我們的測(cè)試,我們想要避免哪些事情呢?

測(cè)試不穩(wěn)定、脆弱、難于調(diào)試。

  • 當(dāng)我們的 app 增加了新功能時(shí),我們的測(cè)試代碼需要花費(fèi)大量時(shí)間來維護(hù)和升級(jí)。
  • 測(cè)試代碼對(duì)于加入團(tuán)隊(duì)的新人來說難于理解。
  • 你可能已經(jīng)理解我所講的內(nèi)容了 ??。

之前很長的時(shí)間,我曾認(rèn)為測(cè)試代碼只是一些我快速堆砌的代碼,因?yàn)橛腥烁嬖V我必須要編寫測(cè)試。我不那么在乎它們的質(zhì)量,因?yàn)槲覍⑺暈橐患嵤?,并不將它放在首位。然而,一旦我因?yàn)榫帉憸y(cè)試而發(fā)現(xiàn)驗(yàn)證自己的代碼有多么快,以及對(duì)自己有多么自信 —— 我對(duì)測(cè)試的態(tài)度就開始了轉(zhuǎn)變。

所現(xiàn)在我相信對(duì)于測(cè)試代碼,和將要移交的產(chǎn)品代碼進(jìn)行同等的高標(biāo)準(zhǔn)要求是非常重要的。因?yàn)槲覀兣涮椎臏y(cè)試是需要我們長期使用、拓展和掌握的,我們理應(yīng)讓這些工作更容易完成。

強(qiáng)制解析的問題

那么這一切與 Swift 中的強(qiáng)制解析有什么關(guān)系呢???

有時(shí)必須要強(qiáng)制解析,很容易編寫一個(gè) “go-to solution” 的測(cè)試。讓我們來看一個(gè)例子,測(cè)試 UserService實(shí)現(xiàn)的登陸機(jī)制是否正常工作:

  1. class UserServiceTests: XCTestCase { 
  2.     func testLoggingIn() { 
  3.         // 為了登陸終端 
  4.         // 構(gòu)建一個(gè)永遠(yuǎn)返回成功的模擬對(duì)象 
  5.         let networkManager = NetworkManagerMock() 
  6.         networkManager.mockResponse(forEndpoint: .login, with: [ 
  7.             "name""John"
  8.             "age": 30 
  9.         ]) 
  10.  
  11.         // 構(gòu)建 service 對(duì)象以及登錄 
  12.         let service = UserService(networkManager: networkManager) 
  13.         service.login(withUsername: "john"password"password"
  14.  
  15.         // 現(xiàn)在我們想要基于已登陸的用戶進(jìn)行斷言, 
  16.         // 這是可選類型,所以我們對(duì)它進(jìn)行強(qiáng)制解析 
  17.         let user = service.loggedInUser! 
  18.         XCTAssertEqual(user.name"John"
  19.         XCTAssertEqual(user.age, 30) 
  20.     } 

如你所見,在進(jìn)行斷言之前,我們強(qiáng)制解析了 service 對(duì)象的 loggedInUser 屬性。像上面這樣的做法并不是絕對(duì)意義上的錯(cuò),但是如果這個(gè)測(cè)試因?yàn)橐恍┰蜷_始失敗,就可能會(huì)導(dǎo)致一些問題。

假設(shè)某人(記住,“某人”可能就是“未來的你自己”??)改變了網(wǎng)絡(luò)部分的代碼,導(dǎo)致上述測(cè)試開始崩潰。如果這樣的事情發(fā)生了,錯(cuò)誤信息可能只會(huì)像下面這樣:

  1. Fatal error: Unexpectedly found nil while unwrapping an Optional value 

盡管用 Xcode 本地運(yùn)行時(shí)這不是個(gè)大問題(因?yàn)殄e(cuò)誤會(huì)被關(guān)聯(lián)地顯示 —— 至少在大多數(shù)時(shí)候 ??),但當(dāng)連續(xù)地整體運(yùn)行整個(gè)項(xiàng)目時(shí),它可能問題重重。上述的錯(cuò)誤信息可能出現(xiàn)在巨大的“文字墻”中,導(dǎo)致難以看出錯(cuò)誤的來源。更嚴(yán)重的是,它會(huì)阻止后續(xù)的測(cè)試被執(zhí)行(因?yàn)闇y(cè)試進(jìn)程會(huì)崩潰),這將導(dǎo)致修復(fù)工作進(jìn)展緩慢并且令人煩躁。

Guard 和 XCTFail

一個(gè)潛在的解決上述問題的方式是簡單地使用 guard 聲明,優(yōu)雅地解析問題中的可選類型,如果解析失敗再調(diào)用 XCTFail 即可,就像下面這樣:

  1. guard let user = service.loggedInUser else { 
  2.     XCTFail("Expected a user to be logged in at this point"
  3.     return 

盡管上述做法在某些情況下是正確的做法,但事實(shí)上我推薦避免使用它 —— 因?yàn)樗蚰愕臏y(cè)試中增加了控制流。為了穩(wěn)定性和可預(yù)測(cè)性,你通常希望測(cè)試只是簡單的遵循 given,when,then 結(jié)構(gòu),并且增加控制流會(huì)使得測(cè)試代碼難于理解。如果你真的非常倒霉,控制流可能成為誤報(bào)的起源(對(duì)此之后的文章會(huì)有更多的相關(guān)內(nèi)容)。

保持可選類型

另一個(gè)方法是讓可選類型一直保持可選。這在某些使用情況下完全可用,包括我們 UserManager 的例子。因?yàn)槲覀儗?duì)已經(jīng)登錄的 user 的 name 和 age 屬性使用了斷言,如果任意一個(gè)屬性為 nil ,我們會(huì)自動(dòng)得到錯(cuò)誤提示。同時(shí)如果我們對(duì) user 使用額外的 XCTAssertNotNil 檢查,我們就能得到一個(gè)非常完整的診斷信息。

  1. let user = service.loggedInUser 
  2. XCTAssertNotNil(user"Expected a user to be logged in at this point"
  3. XCTAssertEqual(user?.name"John"
  4. XCTAssertEqual(user?.age, 30) 

現(xiàn)在如果我們的測(cè)試開始出錯(cuò)了,我們就能得到如下信息:

  1. XCTAssertNotNil failed - Expected a user to be logged in at this point 
  2. XCTAssertEqual failed: ("nil"is not equal to ("Optional("John")"
  3. XCTAssertEqual failed: ("nil"is not equal to ("Optional(30)"

這讓我們能夠更加容易地知道發(fā)生錯(cuò)誤的地方,以及該從哪里入手去調(diào)試、解決這個(gè)錯(cuò)誤 ??。

使用 throw 的測(cè)試

第三個(gè)選擇在某些情況下是非常有用的,就是將返回可選類型的 API 替換為 throwing API。Swift 中的 throwing API 的優(yōu)雅之處在于,需要時(shí)它能夠非常容易地被當(dāng)成可選類型使用。所以很多時(shí)候選擇采用 throwing 方法,不需要犧牲任何的可用性。比如說,假設(shè)我們有一個(gè) EndpointURLFactory 類,被用來在我們的 app 中生成特定終端的 URL,這顯然會(huì)返回可選類型:

  1. class EndpointURLFactory { 
  2.     func makeURL(for endpoint: Endpoint) -> URL? { 
  3.         ... 
  4.     } 

現(xiàn)在我們將其轉(zhuǎn)換為采用 throwing API,像這樣:

  1. class EndpointURLFactory { 
  2.     func makeURL(for endpoint: Endpoint) throws -> URL { 
  3.         ... 
  4.     } 

當(dāng)我們?nèi)匀幌氲玫揭粋€(gè)可選類型的 URL 時(shí),我們只需要使用 try? 命令去調(diào)用它:

  1. let loginEndpoint = try? urlFactory.makeURL(for: .login) 

就測(cè)試而言,上述這種做法的最大好處在于可以在測(cè)試中輕松地使用 try,并且使用 XCTest runner 完全可以毫無代價(jià)地處理無效值。這是鮮為人知的,但事實(shí)上 Swift 測(cè)試可以是 throwing 函數(shù),看看這個(gè):

  1. class EndpointURLFactoryTests: XCTestCase { 
  2.     func testSearchURLContainsQuery() throws { 
  3.         let factory = EndpointURLFactory() 
  4.         let query = "Swift" 
  5.  
  6.         // 因?yàn)槲覀兊臏y(cè)試函數(shù)是 throwing,這里我們可以簡單地采用 'try' 
  7.         let url = try factory.makeURL(for: .search(query)) 
  8.         XCTAssertTrue(url.absoluteString.contains(query)) 
  9.     } 

沒有可選類型,沒有強(qiáng)制解析,某些發(fā)生錯(cuò)誤的時(shí)候也能完美地做出診斷 ??。

使用 require 的可選類型

然而,并不是所有返回可選類型的 API 都可以被替換為 throwing。不過在寫包含可選類型的測(cè)試時(shí),有一個(gè)和 throwing API 同樣好的方法。

讓我們回到最開始 UserManager 的例子。如果既不對(duì) loggedInUser 進(jìn)行強(qiáng)制解析,又不把它看作可選類型,那么我們可以簡單地這樣做:

  1. let user = try require(service.loggedInUser) 
  2. XCTAssertEqual(user.name"John"
  3. XCTAssertEqual(user.age, 30) 

這實(shí)在是太酷了!??這樣我們可以擺脫大量的強(qiáng)制解析,同時(shí)避免讓我們的測(cè)試代碼難于編寫、難于上手。那么為了達(dá)到上述效果我們應(yīng)該怎么做呢?這很簡單,我們只需要對(duì) XCTestCase 增加一個(gè)拓展,讓我們分析任何可選類型表達(dá)式,并且返回非可選的值或者拋出一個(gè)錯(cuò)誤,像這樣:

  1. extension XCTestCase { 
  2.     // 為了能夠輸出優(yōu)雅的錯(cuò)誤信息 
  3.     // 我們遵循 LocallizedErrow 
  4.     private struct RequireError<T>: LocalizedError { 
  5.         let file: StaticString 
  6.         let line: UInt 
  7.  
  8.         // 實(shí)現(xiàn)這個(gè)屬性非常重要 
  9.         // 否則測(cè)試失敗時(shí)我們無法在記錄中優(yōu)雅地輸出錯(cuò)誤信息 
  10.         var errorDescription: String? { 
  11.             return "😱 Required value of type \(T.self) was nil at line \(line) in file \(file)." 
  12.         } 
  13.     } 
  14.  
  15.     // 使用 file 和 line 使得我們能夠自動(dòng)捕獲 
  16.     // 源代碼中出現(xiàn)的相對(duì)應(yīng)的表達(dá)式 
  17.     func require<T>(_ expression: @autoclosure () -> T?, 
  18.                     file: StaticString = #file, 
  19.                     line: UInt = #line) throws -> T { 
  20.         guard let value = expression() else { 
  21.             throw RequireError<T>(file: file, line: line) 
  22.         } 
  23.  
  24.         return value 
  25.     } 

現(xiàn)在有了上述內(nèi)容,如果我們 UserManager 登錄測(cè)試發(fā)生失敗,我們也能得到一個(gè)非常優(yōu)雅的錯(cuò)誤信息,告訴我們錯(cuò)誤發(fā)生的準(zhǔn)確位置。

  1. [UserServiceTests testLoggingIn] : failed: caught error: 😱 Required value of type User was nil at line 97 in file UserServiceTests.swift. 

你可能意識(shí)到這個(gè)技巧來源于我的迷你框架 Require[2], 它對(duì)所有可選類型增加了一個(gè) require() 方法,以提高對(duì)無法避免的強(qiáng)制解析的診斷效果。

總結(jié)

以同樣謹(jǐn)慎的態(tài)度對(duì)待你的應(yīng)用代碼和測(cè)試代碼,在最開始可能有些不適應(yīng),但可以讓長期維護(hù)測(cè)試變的更加簡單 —— 不論是獨(dú)立開發(fā)還是團(tuán)隊(duì)開發(fā)。良好的錯(cuò)誤診斷和錯(cuò)誤信息是其中特別重要的一部分,使用本文中的一些技巧或許能夠讓你在未來避免很多奇怪的問題。

我在測(cè)試代碼中唯一使用強(qiáng)制解析的時(shí)候,就是在構(gòu)建測(cè)試案例的屬性時(shí)。因?yàn)檫@些總是在 setUp 中被創(chuàng)建、tearDown 中被銷毀,我并不把他們當(dāng)作真正的可選類型。正如以往,你同樣需要查看你自己的代碼,根據(jù)你自己的喜好,來權(quán)衡決定。

 

所以你覺得呢?你會(huì)采用一些本文中的技巧,還是你已經(jīng)用了一些相關(guān)的方式?請(qǐng)讓我知道,包括你可能有的任何的問題、評(píng)價(jià)和反饋。

 

責(zé)任編輯:武曉燕 來源: 網(wǎng)羅開發(fā)
相關(guān)推薦

2011-07-27 17:02:12

Xcode iPhone 單元測(cè)試

2017-01-14 23:42:49

單元測(cè)試框架軟件測(cè)試

2016-03-23 10:47:55

Xcode7Swift測(cè)試

2023-04-14 09:04:07

測(cè)試TDBF單元測(cè)試

2017-03-28 12:25:36

2023-07-26 08:58:45

Golang單元測(cè)試

2011-05-16 16:52:09

單元測(cè)試徹底測(cè)試

2017-01-14 23:26:17

單元測(cè)試JUnit測(cè)試

2017-01-16 12:12:29

單元測(cè)試JUnit

2022-12-08 08:01:02

Python測(cè)試單元

2011-06-14 15:56:42

單元測(cè)試

2020-08-18 08:10:02

單元測(cè)試Java

2022-05-12 09:37:03

測(cè)試JUnit開發(fā)

2023-09-20 21:30:14

單元測(cè)試完全指南

2017-03-23 16:02:10

Mock技術(shù)單元測(cè)試

2024-10-16 16:09:32

2021-05-05 11:38:40

TestNGPowerMock單元測(cè)試

2011-07-04 18:16:42

單元測(cè)試

2020-05-07 17:30:49

開發(fā)iOS技術(shù)

2011-06-14 15:39:46

單元測(cè)試
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)