成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

[[421417]]

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

前言

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

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

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

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

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

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

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

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

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

之前很長的時(shí)間,我曾認(rèn)為測試代碼只是一些我快速堆砌的代碼,因?yàn)橛腥烁嬖V我必須要編寫測試。我不那么在乎它們的質(zhì)量,因?yàn)槲覍⑺暈橐患嵤拢⒉粚⑺旁谑孜弧H欢坏┪乙驗(yàn)榫帉憸y試而發(fā)現(xiàn)驗(yàn)證自己的代碼有多么快,以及對自己有多么自信 —— 我對測試的態(tài)度就開始了轉(zhuǎn)變。

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

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

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

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

  1. class UserServiceTests: XCTestCase { 
  2.     func testLoggingIn() { 
  3.         // 為了登陸終端 
  4.         // 構(gòu)建一個永遠(yuǎn)返回成功的模擬對象 
  5.         let networkManager = NetworkManagerMock() 
  6.         networkManager.mockResponse(forEndpoint: .login, with: [ 
  7.             "name""John"
  8.             "age": 30 
  9.         ]) 
  10.  
  11.         // 構(gòu)建 service 對象以及登錄 
  12.         let service = UserService(networkManager: networkManager) 
  13.         service.login(withUsername: "john"password"password"
  14.  
  15.         // 現(xiàn)在我們想要基于已登陸的用戶進(jìn)行斷言, 
  16.         // 這是可選類型,所以我們對它進(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 對象的 loggedInUser 屬性。像上面這樣的做法并不是絕對意義上的錯,但是如果這個測試因?yàn)橐恍┰蜷_始失敗,就可能會導(dǎo)致一些問題。

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

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

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

Guard 和 XCTFail

一個潛在的解決上述問題的方式是簡單地使用 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試中增加了控制流。為了穩(wěn)定性和可預(yù)測性,你通常希望測試只是簡單的遵循 given,when,then 結(jié)構(gòu),并且增加控制流會使得測試代碼難于理解。如果你真的非常倒霉,控制流可能成為誤報(bào)的起源(對此之后的文章會有更多的相關(guān)內(nèi)容)。

保持可選類型

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

  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)在如果我們的測試開始出錯了,我們就能得到如下信息:

  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ā)生錯誤的地方,以及該從哪里入手去調(diào)試、解決這個錯誤 ??。

使用 throw 的測試

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

  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)匀幌氲玫揭粋€可選類型的 URL 時(shí),我們只需要使用 try? 命令去調(diào)用它:

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

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

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

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

使用 require 的可選類型

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

讓我們回到最開始 UserManager 的例子。如果既不對 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í)避免讓我們的測試代碼難于編寫、難于上手。那么為了達(dá)到上述效果我們應(yīng)該怎么做呢?這很簡單,我們只需要對 XCTestCase 增加一個拓展,讓我們分析任何可選類型表達(dá)式,并且返回非可選的值或者拋出一個錯誤,像這樣:

  1. extension XCTestCase { 
  2.     // 為了能夠輸出優(yōu)雅的錯誤信息 
  3.     // 我們遵循 LocallizedErrow 
  4.     private struct RequireError<T>: LocalizedError { 
  5.         let file: StaticString 
  6.         let line: UInt 
  7.  
  8.         // 實(shí)現(xiàn)這個屬性非常重要 
  9.         // 否則測試失敗時(shí)我們無法在記錄中優(yōu)雅地輸出錯誤信息 
  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 使得我們能夠自動捕獲 
  16.     // 源代碼中出現(xiàn)的相對應(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 登錄測試發(fā)生失敗,我們也能得到一個非常優(yōu)雅的錯誤信息,告訴我們錯誤發(fā)生的準(zhǔn)確位置。

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

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

總結(jié)

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

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

 

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

 

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

2011-07-27 17:02:12

Xcode iPhone 單元測試

2017-01-14 23:42:49

單元測試框架軟件測試

2016-03-23 10:47:55

Xcode7Swift測試

2017-03-28 12:25:36

2023-04-14 09:04:07

測試TDBF單元測試

2023-07-26 08:58:45

Golang單元測試

2011-05-16 16:52:09

單元測試徹底測試

2017-01-16 12:12:29

單元測試JUnit

2017-01-14 23:26:17

單元測試JUnit測試

2022-12-08 08:01:02

Python測試單元

2011-06-14 15:56:42

單元測試

2020-08-18 08:10:02

單元測試Java

2022-05-12 09:37:03

測試JUnit開發(fā)

2023-09-20 21:30:14

單元測試完全指南

2017-03-23 16:02:10

Mock技術(shù)單元測試

2021-05-05 11:38:40

TestNGPowerMock單元測試

2024-10-16 16:09:32

2011-07-04 18:16:42

單元測試

2020-05-07 17:30:49

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

2011-06-14 15:39:46

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

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

主站蜘蛛池模板: 免费a大片 | 亚洲综合无码一区二区 | 国产精品成人一区二区三区夜夜夜 | 国产精品免费一区二区三区 | 免费观看av| 99re在线视频 | 福利网站导航 | 我爱操 | 国产美女在线观看 | 国产区视频在线观看 | av资源中文在线天堂 | 亚洲视频区| 国产精品成人在线播放 | 欧美又大粗又爽又黄大片视频 | 精品国产精品一区二区夜夜嗨 | 男女视频在线观看 | 欧美成人精品二区三区99精品 | 在线观看国产三级 | 久久av网 | 国产精品一区在线观看 | 久久久成人免费一区二区 | 亚洲精品乱码久久久久久久久 | 久久久久久久久91 | 伊人导航 | 久久久久久成人 | 秋霞影院一区二区 | 久久久久久高潮国产精品视 | 日韩亚洲欧美综合 | 免费在线观看av的网站 | 中文字幕日韩三级 | 久久久噜噜噜www成人网 | 91天堂网| 久久国产精品精品国产色婷婷 | 激情欧美日韩一区二区 | 中文字幕一区二区三区四区 | 国产精品一区二区欧美黑人喷潮水 | 成人高潮片免费视频欧美 | 亚洲一区久久 | 国产一区二| 天堂色 | 日韩成人性视频 |