好好利用 Swift Protocols 簡單增進程式碼的可測試性


本篇原文(標題: Improving code testability with Swift protocols )刊登於作者 Medium,由 Juanpe Catalán 所著,並授權翻譯及轉載。

swift-protocols

對開發者來說,讓程式碼達到高度的可測試性可以說是一大挑戰。測試是非常有用的,可以確保你撰寫的程式碼運作起來符合需求,而且在添加新功能時也不會發生問題。同時,在一個團隊裡工作時,會有很多人修改程式碼,所以確保程式碼的完整度 (integrity) 也是很重要的。

雖然測試的方式有很多,但它們都不是複雜或難用的。那為什麼很多開發者都不測試程式碼呢?主要的原因(藉口)是沒時間。我相信最大的問題是程式碼在層級、類別、以及外部框架的依賴性之間過於耦合。

在這篇文章中,我希望向大家證明,建立框架的抽象層或是解耦類別並不困難!讓我們開始吧!

情境

想像我們需要開發一個 app,它需要知道使用者的地理位置,因此我們需要使用 CoreLocation

我們的 ViewController 看起來像這樣:

它有一個 CLLocationManager 作為 locationManager,用來請求使用者的地理位置、或是有需要的話向使用者請求權限。同時,它也在遵從 CLLocationManagerDelegate 協定,接收 locationManager 的輸出。

這裡我們可以看到 ViewControllerCoreLocation 產生耦合,以及其他與責任分離有關的問題。

不論如何,讓我們來為 ViewController 製作測試吧。以下會是個不錯的範例:

我們可以看一下 sut (System Under Test) 以及其中一個可能的測試。在那裡,我們請求使用者地理位置,並將其儲存到本地變數 (userLocation) 中。

在這裡,問題開始浮現 ⋯⋯ CLLocationManager 管理這些請求,但這並不是一個同步的流程,所以當我們確認儲存的位置時,它仍然是 nil。而且,我們可能沒有請求地理位置的權限,也就是說在個例子裡,地理位置也會是 nil

現在,讓我們看看一些可行的解決方法!讓我們不測試任何與地理位置相關的東西,來測試 ViewController 吧。建立一個 CLLocationManager 的子類別,然後我們可以模擬方法、或嘗試正確地執行方法,並從類別中解耦 CLLocationManager。在此,我會選擇後者。

協定導向程式設計 (Protocol Oriented Programming, POP) 來救援

Swift 的設計核心是兩個非常強大的概念:協定導向程式設計與類別數值語義 (Class Value Semantics)。
-Apple

協定導向程式設計對開發者來說是一個強大的工具,而 Swift 無庸置疑是個協定導向的程式語言。所以要解決這些相依性的問題,我決定使用協定。

首先,為了抽象化 CLLocation,我們會定義一個協定,它會包含程式碼需要的變數或函式。

現在,我們可以在沒有 CoreLocation 的情況下取得地理位置。仔細分析 ViewController 的話,就會看到我們並不是真的需要 CLLocationManager,它只是在我們請求時,提供使用者位置的人。因此,我們會建立一個包含我們需求的協定,而符合此協定的任何人都可以成為提供者。

在這次的範例中,我們已經建立了 UserLocationProvider。這個協定規定我們只需要一個方法來請求使用者的位置資訊,而請求的結果會透過我們提供的回呼 (Callback) 來回傳。

我們已準備好建立一個 UserLocationService 來符合協定,並向我們提供位置資訊。藉由這個方式,我們解決了類別中 CoreLocation 的相依性問題。不過,似乎還有一些問題我們還沒解決 😅。

協定再次來救援了,我們只需建立一個新協定,來指定位置資訊提供者:

我們擴展了 CLLocationManager 的功能,使其符合我們的新協定。

然後現在,我們準備好建立 UserLocationService 了 🎉。它看起來會像這樣:

UserLocationService 有自己的位置資訊提供者,但它不會知道提供者是誰,這對它來說並不重要。它只需要在請求時得到使用者的位置資訊,其他的微不是它的責任範圍了。

這個符合 CLLocationManagerDelegate 協定的擴展是必需的,因為我們將會使用 CoreLocation。但是,我們在測試中會看到怎樣的情況呢?其實我們並不真的需要它來確認類別是否運作正常。

我們可以添加任何種類的委派 (Delegate) 到協定裡,不過,我想對這次的範例來說這樣可能太多了。

在我們開始測試之前,來看看使用 UserLocationProvider 而不是 CLLocationManagerViewController 是怎樣的:

看到這個程式碼,我們現在可以做個結論,我們的 ViewController 程式碼較少、擔負的責任較少、可測試性更佳。

測試

讓我們開始測試吧!首先,我們會建立一些用來測試 ViewController 的模擬類別。

使用這些模擬類別,我們可以在注入任何所需的結果,我們會模擬一個 UserLocationProvider 的運作,並專注在真正的目標 ── ViewController 上。

我已經創建了兩個測試,一個用來確認沒有請求位置資訊的權限時,提供者不會提供任何東西;而另一個則是相反的情況,如果我們獲得授權,就應該能夠取得使用者的位置資訊。而就如你所見,這些測試都順利通過了!!✅ 💪

除了 ViewController 之外,我們還創建了一個額外的類別 UserLocationService,所以我們的測試應該也要將它包含在內。

因為 LocationProvider 不是這個測試的目標,所以它需要另外的模擬。

我們可以建立許多測試項目,其中一個可以是:確認提供者是否說我們已經有了權限,如果還沒有就請求權限,如果有權限就可以請求位置資訊。

小結

你可以想像到,要解耦程式碼有很多方式,而這次的文章只是其中一種,但這是個範例很好地證明了測試並不困難。

還記得文章開頭的圖片嗎?圖片中你可以看到樂高積木,這完美地解釋了甚麼是解耦及抽象化元件。在最後,它被定義為一種特定的連接方式(不過顏色並不重要)。

或許最煩悶的工作就是建立模擬資料,不過現在已經有程式庫與工具來簡化這項工作,像是 Sourcery。另外,我的同事 Hugo Peral 亦撰寫了一篇文章,說明如何使用 Sourcery 來節省測試時間;而 John Sundell這篇文章也提供了製作模擬資料的細節。

感謝你閱讀這篇文章。如果你覺得這篇文章有幫助的話,歡迎向其他人分享 😉。如果有任何疑問或建議,歡迎你在下面留言。

本篇原文(標題: Improving code testability with Swift protocols )刊登於作者 Medium,由 Juanpe Catalán 所著,並授權翻譯及轉載。
作者簡介:Juanpe Catalán,iOS 開發人員和產品經理。他使用 Swift 和 Objective-C 語法,十分熟悉 Cocos2D、NodeJS、Angular 等,喜歡乾淨的程式碼,並致力為 Open Source 社區貢獻。他亦是 Github 一個流行開源庫@SkeletonView 的創辦人。歡迎在 Twitter 或電郵到 [email protected] 與我聯絡。
譯者簡介:楊敦凱-目前於科技公司擔任 iOS Developer,工作之餘開發自有 iOS App 同時關注網路上有趣的新玩意、話題及科技資訊。平時的興趣則是與自身專業無關的歷史、地理、棒球。來信請寄到:[email protected]

此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
訂閲電子報

訂閲電子報

AppCoda致力於發佈優質iOS程式教學,你不必每天上站,輸入你的電子郵件地址訂閱網站的最新教學文章。每當有新文章發佈,我們會使用電子郵件通知你。

已收你的指示。請你檢查你的電郵,我們已寄出一封認證信,點擊信中鏈結才算完成訂閱。

Shares
Share This