iT邦幫忙

2023 iThome 鐵人賽

DAY 3
0
Mobile Development

在 iOS 專案上加上 Unit testing - 因為 You need testing系列 第 3

D3 - 在 iOS 專案加上測試-You need testing {可測試的程式碼的通常長什麼樣子}

  • 分享至 

  • xImage
  •  

程式碼都可以測試嗎? - 你該問的應該是,這一段程式碼測試起來有意義嗎?

在談 Unit Test 之前,先談談 SOLID 原則

  • S - SRP 單一職責原則, 一個 func (method) 只做一件事情
  • 情境: Client 端發動 API Request -> 拿到資料後解析 -> 寫入本地端或 DB
  • 問題: 你會怎麼設計這個情境的程式碼? 讓他能被 Unit Test 測試?

• 你如果寫了一個 function (method),讓他在裡面處理了 http request,再處理了 parse data,再處理了 DB 寫入。會怎樣呢?

func downloadAndParseAndWriteInDB() {
    
    /// 從這個 url 拿到 user 的資訊
    guard let url = URL(string: "https://mydomain.com/getUserInfo") else {
        print("invalid url")
        /// handle error here...
        return
    }
    
    let request = URLRequest(url: url)
    URLSession.shared.dataTask(with: url) { data, response, error in
        
        /// handle error....
        
        /// 如果 response 有 data, 後端的 spec 為 [String: Any] 的 json,但通常我們會建立好物件
        /// 使用物件在 app 內部進行傳接值,而不是 dict 傳接值
        if let data  {
            
            do {
                
                let userInfo = try JSONDecoder().decode(UserInfo.self, from: data)
                
                /// 這個 userInfo 物件有三個值: userID, firstName, lastName
                print("user id: \(userInfo.userID)")
                print("user firstName: \(userInfo.firstName)")
                print("user lastName: \(userInfo.lastName)")
                
                /// 寫入 Database
                LocalStorageAdapter.write(userInfo: userInfo)
            } catch {
                print("json decode error: \(error)")
            }
            
        }
    }.resume()
}

1.這段程式碼耦合了 request, parse, I/O

2.只要需求變更,你至少就要檢查 12 行 到 32 行

3.如果你對這個 func 寫了 test,當他 fail 的時候,你需要判斷這三十行左右的程式碼中,錯誤是發生在 URLRequest 階段,還是解析 JSON,還是在寫 Database 的時候產生的。

可以被測試的程式碼

讓 Data Requester 進行 request, 讓 Data Parser 處理 Parser, 讓 DB writer 做讀寫。這三個 function 才能被測試,當你的子 function 都能被測試的時候,會有兩大優勢。

func testableDownloadAndParseAndWriteInDB() async {
    
    /// 從這個 url 拿到 user 的資訊
    guard let url = URL(string: "https://mydomain.com/getUserInfo") else {
        print("invalid url")
        /// handle error here...
        return
    }
    
    do {
        
        let data = try await NetworkManager.shared.getUserInfo().get()
        
        let userInfo = DataParser().parseToUserInfo(from: data)
        
        LocalStorageAdapter.write(userInfo: userInfo)
    } catch {
        /// handle error here
        print("Got error: \(error)")
    }
}

1.只要這段程式碼出問題,你可以知道問題發生在 NetworkManager ? Or parser ? Or LocalStorageAdapter?

2. 進行需求變更的時候,你只要針對變更部分進行程式碼變更就好,你不用掃過三個子 func

舉例來說,當需求變更時,後端增加 user model key 值的時候, 你覺得 NetworkManager 和 LocalStorageAdapter 要變更嗎?


上一篇
D2 - 在 iOS 專案加上測試-You need testing {開 Unit Testing Target 的方法}
下一篇
D4 - 在 iOS 專案加上測試-You need testing {情境假設: 在專案中擴充 feat 時,你會遇到的狀況}
系列文
在 iOS 專案上加上 Unit testing - 因為 You need testing32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言