APP重構之路(三) 引入單元測試

杜瑋發表於2018-08-22

重構的時候我們需要一個模具,讓我們能夠大膽修改的同時確保結果的正確性,這個時候就要引入“單元測試”了。

前言

本文沒有給出任何測試程式碼,或者是在教你如何編寫一份具有良好測試性的程式碼,而是闡述在重構過程中單元測試的重要性與實現方法,關於程式碼可測試性相關的內容我會另開一篇文章去具體闡述。(畫個餅)

一、為什麼要引入單元測試

在開發過程中我們會遇到這樣一些問題:

  • 面對需要重構龐大的模組程式碼時無從下手
  • 修改了一處地方卻在另一處地方引發了新的bug
  • 擴充套件新功能的同時導致舊程式碼出現bug
  • 在測試人員難以覆蓋到的基礎功能介面出現了bug
  • 出現了一種難以重現的特殊邊界條件觸發的bug

另外我們也許還會遇到一些這樣的模組:

  • A模組依賴於B模組的結果,但是B模組尚未開發完成
  • 模組狀態過於複雜,手工測試需要耗費大量時間
  • 模組業務與時間節點相關,手工測試難以覆蓋

這個時候也許能夠利用經驗和豐富的debug技巧來解決這些問題,但是很多時候我們的處理並不完美,因為我們缺少了一個規範,在編碼過程中難以顧及其他模組的影響,這個時候,我們就需要引入單元測試

二、單元測試的價值

可維護性增強

當在對程式碼進行修改時,利用單元測試就能夠清晰的知道是否破壞了老的業務邏輯,這樣大大減少了迴歸出錯的可能性。而當我們從測試那裡獲得了一個bug時,就可以通過測試用例去還原,當我們這個測試通過後,這個bug也就解決了,而且這個bug fix的測試用例也保證了這個bug以後不會再次出現。

降低重構難度

有了單元測試的保障,我們可以比較大膽的進行重構設計,而單元測試也會成為重構時很好的一個模具。當然在重構時也需要對單元測試進行重構,但是和可靠性相比,這種額外的負擔是值得去承受的

減少除錯時間

在除錯中,我們很多時候都需要花費一些額外的時間來觸發需要除錯的程式碼,但是在單元測試中,我們能夠針對需要除錯的程式碼構建相關的測試用例,方便的進行反覆的測試與模擬,大大減少了除錯的時間。

減少低階錯誤

測試的存在價值就是為我們發現並解決錯誤,單元測試更是如此,當我們對自己的程式碼進行單元測試時,就能容易的排除掉一些非常低階的錯誤,起碼我們能夠保證在一些正常的情況下程式碼是可以正確工作的。

描述程式碼

好的程式碼就是一份好的文件,單元測試更是如此。一份好的單元測試能夠描述在對應的情況下,程式碼應該有如何的預期表現,那麼別人只需要檢視測試用例就能清楚的知道程式碼的功能。

提高程式碼質量

一份程式碼如果和其他程式碼強耦合,它是難以被測試的,所以為了測試,開發人員會被驅使寫出低耦合、可擴充套件的程式碼。

三、單元測試方案

單元測試中有測試驅動開發(TDD)與行為驅動開發(BDD)兩種思路

測試驅動開發(TDD)

  • 根據需求與介面先編寫測試用例
  • 根據測試用例編寫業務程式碼
  • 開發效率低
  • 資源耗費大
  • 測試覆蓋率高

行為驅動開發(BDD)

  • 通過測試用例描述程式碼行為
  • 通過自動執行測試用例快速反饋
  • 通過Mock作為相關程式碼模組的替身
  • 開發效率較高
  • 資源耗費較低
  • 測試覆蓋率較TDD要低

基於目前專案的情況與開發流程,我選擇了BDD作為測試框架,並會選擇使用XCTest + OCMock + OCHamcrest的方案,以下是三個框架的介紹:

XCTest

XCTest 可以完成的事

  • 基本斷言的邏輯判斷
  • 非同步測試
  • 效能測試

為什麼選擇 XCTest

  • XCode原生的測試框架,能夠更好適應Apple之後的更新
  • XCTest有大量文件支援,上手難度較低
  • XCTest新增進專案後只是作為專案測試框架,並不會影響到打包等一些東西

OCMock

為什麼需要 OCMock

mock即為模擬,OCMock可以偽造(模擬)一個物件,給它一些預設的值之類的,並進行對應的驗證

比如在我需要測試WiFi直連模組時,我需要一個WiFi才能測試直連功能,這個時候我們就可以利用OCMock,去模擬一個WiFi物件,它可以是模擬成風險WiFi,也可以模擬成免費WiFi,這樣我們的直連模組的測試就完全獨立於WiFi物件,可以方便的進行測試。

OCMock 可以完成的事

  • 建立一個模擬物件,模擬一個特定物件的行為,排除一些外部類的干擾
  • 構造自己的用例進行驗證
  • 對已有方法進行重定義,以自己定義的邏輯進行互動
  • 判斷函式是否執行過

為什麼選擇 OCMock

  • 原生XCTest並不支援Mock功能
  • OCMock是專門為iOS與OS X進行Mock測試的開源專案,擁有超過5000+ app使用,1100萬+下載量
  • OCMock使用Apache 2.0協議,能夠在需要時候修改程式碼滿足需要並作為開源或商用產品釋出/銷售
  • OCMock有官方文件,資料齊全

OCHamcrest

OCHamcrest 可以完成的事

  • 更高階的斷言
  • 斷言可擴充套件性
  • 支援結構體的斷言

為什麼選擇 OCHamcrest

  • 相對於另一個斷言框架 Expecta ,OCHamcrest更為成熟,Expecta可能會導致斷言結果錯誤
  • XCTest 內建斷言並不充分,複雜條件下的判斷需要編寫大量斷言程式碼
  • 利用OCHamcrest的擴充套件效能夠將格式化的自定義log輸出到日誌檔案,提供更多可以定製化而且詳細的資訊

四、整體測試框架

APP重構之路(三) 引入單元測試

五、應該測試什麼 應該怎麼測試

測試的原則

  • 快速:這樣才不會介意去執行
  • 獨立:一個測試不應該耦合於另一個測試
  • 可重複:每次測試的結果應該一致
  • 可驗證:結果應該是成功/失敗,而不是一個解釋性的日誌文件

測試的內容

在寫任何測試前,應該明確應該要測試什麼,一般的情況下,單元測試應該包括這些內容:

  • 核心功能測試
  • 邊界條件
  • 錯誤處理

測試的思路

針對目前專案情況,我會使用單元測試與人工測試相結合的方式去進行,因為目前我們大部分功能都是與UI牽連,不能完全依靠單元測試去完成所有的測試工作,但是我們可以將邏輯部分進行分離,舉網路連線模組為例:

請求流程

APP重構之路(三) 引入單元測試

我們可以對介面相關的部分測試進行拆解,在邏輯部分實現單元測試,而人工測試部分單純檢查整個直連流程和介面部分是否正常。

這樣能夠避免人工測試時依賴於邏輯的情況,比如在需要測試發起100個請求後模組是否會出現問題時,無需依靠手工去真的連線100次,只需要在單元測試中模擬進行100次連線,並檢視結果是否正確就可以。

應該測試的物件

在專案中,我們有大量的類,全部覆蓋單元測試是不現實的,我們需要進行挑選。以下是我列舉的一些因素。

1.資料相關

比如在本地資料儲存模組中,我們需要儲存不同的資料,這時候我們可以通過單元測試構造不同的測試資料進行儲存,檢視是否儲存成功,資料部分是單元測試最需要覆蓋的部分。

2.邏輯相關

比如在連線模組中,需要對部分請求結果進行過濾,這就是一個邏輯,針對這種邏輯,可以在單元測試中進行測試是否過濾成功,而人工測試則無需關注過濾的邏輯,僅僅需要關注過濾後介面是否正常顯示。

3.多狀態的模組

比如在連線模組中,連線的狀態就有8種,包括了連通性檢查、連線中、已連線等,這些狀態能夠利用單元測試很好的模擬出來,這樣就解決了人工測試下難以模擬不同狀態轉換的問題。

六、總結

我們寫程式碼最終的目的只有兩個:實現需求與提高程式碼質量,在保證完成需求的前提下,增加單元測試能提高程式碼的質量與可維護性,縱使在引入了單元測試後,我們也許會面臨增加了研發的程式碼量,花費更多精力在編寫單元測試上,增加了開發成本,但我認為相比於單元測試帶來的優勢,這些是能夠克服的。

更多內容可以關注我的部落格

相關文章