Unit Test 教學:非同步測試 API

David Wu
7 min readApr 18, 2019

--

前一篇 Unit Test 教學 是分享測試的核心精神與簡短的驅動測試開發(Test-Driven Development),本篇就是稍稍進階一點的非同步測試。本次就直接以打 API 以及解析 JSON 為例,一步步直接讓你可以應用。範例程式碼在我的 github

測試 API 是死是活

網路測試會切成 web api 死活測試Parsing 測試。web api 死活測試可以把前端後端的職責切得很明確。

Note: 教學文使用純 get url 沒有帶 headers,github 上的範例專案有附 headers。post 可以在掌握概念之後可以自行練習 :)

我傾向把測試的功能分類好,所以就先拉出一個 APITests 吧!

跑每個 test 都會依序經過 setup() 、test function、tearDown() 。慣例上是會把要測試的系統(System Under Test)直接放在這個 XCTestCase 的 scope 上,然後每次經過 setup()tearDown() 就把 SUT 重置。

為了節省各位看官們的時間,就引用 iOS Unit Testing and UI Testing Tutorial ,然後我修改好的 function 來用唄!請直接在 test case 加入下面的 helper function。

如此一來,只要把 urlString 丟進去就可以測該 API 啦!

上面那段程式碼要知道的是 expectationfulfill():它是描述你預期的事情,他有個 method fulfill() 會告訴電腦 wait可以結束了。

wait 則是要等待哪些 expectation 以及最多等待多久。以本例而言,我 waitpromise 這個 expectation,最多等 5 秒。

註:在 iOS Unit Testing and UI Testing Tutorial 測 API 有兩種寫法,我在此只介紹比較好的那個,因為另外一個比較耗時,而且不如這一個 given, when, then 的結構化。

測試 APIManager

通常我們會想要把呼叫 API 的部分模組化,這次就用一邊寫 APIManager,一邊測試來開發一個吧!(不完整版的 TDD,但是很實際)。

跟上面一樣先開一個 APIManagerTests 的 Unit Test Class,並設定好 SUT,也就是 APIManager

這時候 compiler 會說還沒有 APIManager ,那就回到專案裡建立一個。

因為 Swift 5 導入了 Result 型別,所以這次我就把 Result 型別跟(Data?, URLResponse?, Error?) 的版本都列出來,看官們自己決定要用哪一個。

另外,為了之後測試「解析假資料(stub)」方便,請複製下面的這些程式碼並建立在 DHURLSessionMock.swift 裡,要為 URLSession 建立 mock。

DHURLSessionMock.swift

給予正確的 URL 應該是要能夠回傳 Data,而 Error 應為 nil ;失敗的 URL 應該 Error 不為 nil,Data 為 nil 。下面是使用 Result 型別的版本:

Version: Result Type

記得可以把 url 微調成錯的,用 red-green-refactor 確保測試是正常運作。還有,可以在主程式下斷點幫助 Debug!

再來是使用 (Data?, URLResponse?, Error?) 的版本:

Version: (Data?, URLResponse?, Error?)->Void

測試 解析 JSON

這次的 SUT 是 WeatherManager,要看它能否把 JSON 解析出來。首先,我們先建立一個 stub (假資料),weather.json 在 TestTarget 裡面。等等直接解析它。這次 weather.json 的資料就直接從這裡複製

你一定會覺得很奇怪,為什麼不測實際的 API,而要另外測假資料?因為大部分的情況下,API 都是動態的,JSON 的值變來變去,沒辦法寫 XCTAssert。另外,就是打 web API 相對吃資源跟時間。

先建立一個 WeatherManagerTests,然後把程式碼換成跟下面一樣

setup() 的部分,我們把假資料 weather.json 轉換成 Data。用假的 URL 建立 帶有 statusCode 200 的 URLResponse,再把 Data 跟 URLResponse 建立成 URLSessionMock。最後,再讓 weatherManager 去測試解析 URLSessionMock 即可。

在寫這個測試的期間,當然是一邊開發自己的 WeatherManager:

在公司會遇到一種情況就是:明明就 API 死活測試 跟 本機端的 JSON 檔解析測試 都過了,但是為什麼 App 的資料有問題?那麼就極有可能是後端把 API 的格式換了,卻沒有跟前端說,這在職場屢見不鮮。

測試 View Controller?

在 Unit Test 裡面,其實就只是把 SUT 換成你要的 View Controller 而已。在這裡只是要講一點注意事項,測試的程式如果會經過 IBOutlet,那一定要下viewController.loadview(),否則會 crash 給你看。Unit Test 是拿來測運算邏輯跟資料流,如果要測 UI,那就得用 UI test。

加了 Test Target 卻 build 不起來?

這個情況常常是因為你使用了 CocoaPods,卻沒有在 Podfile 叫這個 target 去引用 framework。解法如下:

target 'MyAppTests' do
inherit! :complete
end

總結

  • System Under Test(SUT)是我們想要測試的物件,可以是 APIManager 或是 View Controller。
  • 跑 test function 執行時會依序經過 setup() 、test function、tearDown() 。我們會在 setup()tearDown() 重新設定 SUT。
  • 非同步測試會用到 expectationexpectation.fufill()wait(for:timeout)
    expectation 是你期望的結果,要放進wait(for:timeout) 裡面。
    expectation.fufill() 是告訴電腦該wait 到什麼時候,通常都是放在 closure 最後一行。
  • 網路測試會切成 web api 死活測試Parsing 測試。web api 死活測試可以把前端後端的職責切得很明確。Parsing 測試會用 stub 假資料來測,這樣才可以測靜態的資料,也會會跑得比較並降低 server 負擔。
  • 下一步該去哪兒?Unit Test 教學:覆蓋率 幫你找到哪裡可以改善,把程式碼會變得更完善一些。

--

--