iOS
unittest
XCTest
Swift

在 iOS 專案 Mock 心得文和意料之外的收穫

More than 1 year has passed since last update.

最近開始做新的專案,每一次開新專案總是會有一些新的期許和跨出幾步。

前一個專案因為和同事達成共識,所以開始寫部分的單元測試,
這次就開始思考要怎麼 mock 一些相依(耦合)的物件。

那 mock 是什麼呢?
簡單的來說,他是測試替身(test double)的其中一種。
為了可以讓 "目標物件"(SUT, Software Under Test) 可以被獨立測試,就必須隔離與 "相依外部物件"(DOC, Depended-on Component) 的互動

對象

這次要 mock 的對象是網路存取層,
以目前的專業來說,網路存取的部分也就是使用 Alamofire 的那一層。

過去的做法

過去的做法我覺得比較不好,當時我們是用了一個叫做 "OHHTTPStubs" 的這個 library ,他是透過直接介入網路存取層,來達成攔截及回傳指定 response 的效果。

不過這個工具還算不錯,有讓我們達成測試串接 web API 時處理回應的部分。

哪裡不好?

總覺得哪裡怪怪的,是不是

「沒有辦法達成抽換 mock ,才會用這個方式攔截網路相關的 requests ?」

於是這也是我這次決定要嘗試,連網路存取這個部分,都能夠由自己抽換掉的嘗試。

目標

  • 不透過第三方套件,就能達成這件事
  • 合理的配合物件的可測,而進行重構

技術疊之一 → Protocol

在這一段時間,大概是這個月初(2016/9),
開始比較熟悉的搭配 Swift 的 protocol 來開發。
(也就是一般人說得針對介面來開發)

類別的行為也能夠開始定義的比較明確、
要替換不同行為的物件,只要有配合指定的 protocol ,
就可以很輕易的抽換掉而不用改太多的東西。

當時心裡就有開始有感覺:

這應該就是可以用在測試時,抽換相依物件的技巧

所以我覺得能夠 熟悉 怎麼利用 protocol 來開發,是一件很重要的事情。

意外的收穫 → Mock 和職責分離的關係

這幾天在寫測試的時候,有會發現有些東西抽不掉。
以我這次碰到的情形來說,就是包裝 Alamofire 的那一層。

即使在設計接口(protocol)和實作的時候會思考職責分離這件事,
不過還是會有意料之外的事情會發生。

原本在設計 protocol 的時候,雖然是有發現怪怪的地方,
但是因為動的起來,就先沒有想太多。
結果在寫測試的時候就發現抽不掉了(囧),
於是就動手來重構。

碰到的問題

(這一段沒有看實際的 code 可能會看不懂,會盡可能地寫清楚)

再審視過相關的 protocols 的時候,
就開始發現像是 network client (Alamofire 的包裝類別),
就不能是設定 API 路徑(route)的成員變數。

這樣導致要測試發送 request 和處理 response 的 service classes 的時候,
因為在設計上, service class 有相依 route ,
要 mock 網路存取時,就必須要先 mock route ,才有辦法 mock network client 。

當下就是想「唉呀!」,怪怪的感覺又浮出來了,
不能這樣寫呀, network client 照理來說也不能被包在 route 裡面,
於是就針對這個地方再做重新調整。

重構後的結果

在過程中經歷過一次刪除一個多餘 protocol ,調整上一小節的提到職責不正確的地方。
終於達成自由 mock 網路回應的目標了。

而針對職責明確而重構這件事,也是在要達成 mock 這個目標意外的收穫。

心得和需要注意的地方

Protocol 中要抽換的物件的寫法

因為要可以抽換他,就必須要讓使用 protocol 的 class 或是 struct 可以替換,
所以定義時 getter 和 setter 都要加上。

protocol MYProtocol {
var something: Any  { get set }
}

隨時注意職責分離

必須無時無刻的思考每一個 protocol 或是 class 應該或是不應該做什麼事情,不恰當的相依可能會導致

  • 模糊了這個 protocol 或是 class 的真正用途
  • 在測試時,要抽換 mock 時,會導致抽不了或是多做工

Server Side 的部分

這個部分的注意點就比較跟測試沒關係,
內容上其實也和前一個專案的心得相同。

Web API 必須要:

  • 制定好成功與失敗的回應
  • 必須要處理好錯誤結果的回應

第一個,制定好回應的規範,可以讓介接的 client 端知道丟什麼會發生什麼事,
並針對會發生的事情:
1. 實作對應的 UI 回饋
2. 撰寫針對同一支 API 不同回應的測試,確認都有完整的根據約定處理到

第二個就是要處理錯誤結果:
1. 丟了非約定的內容,必須要有對應的錯誤訊息回傳;不能直接跳 exception 就回吐 HTTP 500 ,讓 client 端無法適當的反應在畫面上。
2. 當有約定的錯誤結果時,必須要遵守。而不會不同的字串 request ,而有時沒處理到或沒接到錯誤。

下一步

應該就是 UI 相關的測試了吧!

UI 本身又 UI 類別(UIViewController, UIView 等等)的測試,以及 UI automation 的測試,
主要會先從 UI 類別先著手,
這些都再找機會來嘗試唄!