Swift之深入解析如何避免单元测试中的强制解析
生活随笔
收集整理的這篇文章主要介紹了
Swift之深入解析如何避免单元测试中的强制解析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、前言
- 強制解析(使用 !)是 Swift 語言中不可或缺的一個重要特點(特別是和 Objective-C 的接口混合使用時),它回避了一些其他問題,使得 Swift 語言變得更加優秀。
- 比如在我的博客 Swift之深入解析如何處理非可選的可選項類型 中,在項目邏輯需要時使用強制解析去處理可選類型,將導致一些離奇的情況和崩潰。
- 因此,盡可能地避免使用強制解析,將有助于搭建更加穩定的應用,并且在發生錯誤時提供更好的報錯信息。那么如果是編寫測試時,情況會怎么樣呢?安全地處理可選類型和未知類型需要大量的代碼,問題就在于我們是否愿意為編寫測試做所有的額外工作,這就是我們需要探討的問題。
二、測試代碼 vs 產品代碼
- 當編寫測試代碼時,我們經常明確區分測試代碼和產品代碼,盡管保持這兩部分代碼的分離十分重要(我們不希望意外地讓模擬測試對象成為 App Store 上架的部分),但就代碼質量來說,沒有必要進行明顯區分。
- 如果思考一下的話,想要對移交給使用者的代碼進行高標準的要求,原因是什么呢?
-
- 想要 App 為使用者穩定、流暢地運行;
-
- 想要 App 在未來易于維護和修改;
-
- 想要更容易讓新人融入我們的團隊。
- 現在如果反過來考慮我們的測試,想要避免哪些事情呢?
-
- 測試不穩定、脆弱、難于調試;
-
- 當我們的 App 增加了新功能時,測試代碼需要花費大量時間來維護和升級;
-
- 測試代碼對于加入團隊的新人來說難于理解。
- 之前很長的時間,我曾認為測試代碼只是一些我快速堆砌的代碼,因為有人告訴我必須要編寫測試。我不那么在乎它們的質量,因為我將它視為一件瑣事,并不將它放在首位。然而,一旦我因為編寫測試而發現驗證自己的代碼有多么快,以及對自己有多么自信,我對測試的態度就開始了轉變。
- 所現在我相信對于測試代碼,和將要移交的產品代碼進行同等的高標準要求是非常重要的,因為我們配套的測試是需要長期使用、拓展和掌握的,理應讓這些工作更容易完成。
三、強制解析的問題
- 那么這一切與 Swift 中的強制解析有什么關系呢?有時必須要強制解析,很容易編寫一個 “go-to solution” 的測試,來看一個例子,測試 UserService 實現的登陸機制是否正常工作:
- 如你所見,在進行斷言之前,我們強制解析了 service 對象的 loggedInUser 屬性。像上面這樣的做法并不是絕對意義上的錯,但是如果這個測試因為一些原因開始失敗,就可能會導致一些問題。
- 假設某人(“某人”可能就是“未來的我們自己”)改變了網絡部分的代碼,導致上述測試開始崩潰。如果這樣的事情發生了,錯誤信息可能只會像下面這樣:
- 盡管用 Xcode 本地運行時這不是個大問題(因為錯誤會被關聯地顯示,至少在大多數時候),但當連續地整體運行整個項目時,它可能問題重重。上述的錯誤信息可能出現在巨大的“文字墻”中,導致難以看出錯誤的來源。更嚴重的是,它會阻止后續的測試被執行(因為測試進程會崩潰),這將導致修復工作進展緩慢并且令人煩躁。
四、Guard 和 XCTFail
- 一個潛在的解決上述問題的方式是簡單地使用 guard 聲明,優雅地解析問題中的可選類型,如果解析失敗再調用 XCTFail 即可,就像下面這樣:
- 盡管上述做法在某些情況下是正確的做法,但事實上我推薦避免使用它,因為它向測試中增加了控制流。為了穩定性和可預測性,通常希望測試只是簡單的遵循 given,when,then 結構,并且增加控制流會使得測試代碼難于理解。
五、保持可選類型
- 另一個方法是讓可選類型一直保持可選,這在某些使用情況下完全可用,包括 UserManager 的例子。因為對已經登錄的 user 的 name 和 age 屬性使用了斷言,如果任意一個屬性為 nil ,我們會自動得到錯誤提示。同時如果對 user 使用額外的 XCTAssertNotNil 檢查,就能得到一個非常完整的診斷信息:
- 現在如果測試開始出錯,就能得到如下信息:
- 這讓我們能夠更加容易地知道發生錯誤的地方,以及該從哪里入手去調試、解決這個錯誤。
六、使用 throw 的測試
- 第三個選擇在某些情況下是非常有用的,就是將返回可選類型的 API 替換為 throwing API。Swift 中的 throwing API 的優雅之處在于,需要時它能夠非常容易地被當成可選類型使用。所以很多時候選擇采用 throwing 方法,不需要犧牲任何的可用性。比如說,假設有一個 EndpointURLFactory 類,被用來在 App 中生成特定終端的 URL,這顯然會返回可選類型:
- 現在將其轉換為采用 throwing API,像這樣:
- 當我們仍然想得到一個可選類型的 URL 時,只需要使用 try? 命令去調用它:
- 就測試而言,上述這種做法的最大好處在于可以在測試中輕松地使用 try,并且使用 XCTest runner 完全可以毫無代價地處理無效值。這是鮮為人知的,但事實上 Swift 測試可以是 throwing 函數,看看這個:
- 沒有可選類型,沒有強制解析,某些發生錯誤的時候也能完美地做出診斷。
七、使用 require 的可選類型
- 然而,并不是所有返回可選類型的 API 都可以被替換為 throwing,不過在寫包含可選類型的測試時,有一個和 throwing API 同樣好的方法。
- 回到最開始 UserManager 的例子,如果既不對 loggedInUser 進行強制解析,又不把它看作可選類型,那么可以簡單地這樣做:
- 這實在是太酷了,這樣就可以擺脫大量的強制解析,同時避免讓測試代碼難于編寫、難于上手。那么為了達到上述效果應該怎么做呢?這很簡單,只需要對 XCTestCase 增加一個拓展,讓我們分析任何可選類型表達式,并且返回非可選的值或者拋出一個錯誤,像這樣:
- 現在有了上述內容,如果 UserManager 登錄測試發生失敗,也能得到一個非常優雅的錯誤信息,告訴我們錯誤發生的準確位置:
- 它對所有可選類型增加了一個 require() 方法,以提高對無法避免的強制解析的診斷效果。請參考:Require。
八、總結
- 以同樣謹慎的態度對待應用代碼和測試代碼,在最開始可能有些不適應,但可以讓長期維護測試變的更加簡單,不論是獨立開發還是團隊開發,良好的錯誤診斷和錯誤信息是其中特別重要的一部分,使用本文中的一些技巧或許能夠讓你在未來避免很多奇怪的問題。
- 我在測試代碼中唯一使用強制解析的時候,就是在構建測試案例的屬性時。因為這些總是在 setup 中被創建、tearDown 中被銷毀,我并不把它們當作真正的可選類型。正如以往,你同樣需要查看你自己的代碼,根據你自己的喜好,來權衡決定。
九、參考資料
- Handling non-optional optionals in Swift。
總結
以上是生活随笔為你收集整理的Swift之深入解析如何避免单元测试中的强制解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python之打造专属Python开发者
- 下一篇: Python之深入解析一行代码计算每个省