iT邦幫忙

2022 iThome 鐵人賽

DAY 28
1
Mobile Development

關於 Flutter 開發的一些設計雜談系列 第 28

Day 28 - 為程式撰寫單元測試

  • 分享至 

  • xImage
  •  

大多時候,我們花許多時間在開發功能,隨著功能越來越多,功能之間也互相影響。有時候,我們改了一個功能,另外一個功能卻壞了,但是我們卻沒發現。如果有 QA 幫忙測試,或許還能透過專業的測試手法找到,萬一今天只有我們自己測試,而我們又沒花太多時間測試,就會把 Bug release 出去了。

為此,我們可以進行單元測試,測試我們開發的功能。當我們每次新增功能,也為他加上測試,這些測試就能保護我們的功能,在我們因為新增其他功能而改壞或者重構有問題時,就會在我們執行時讓我們知道,哪些地方改壞了。

開始測試

在這邊就讓我們測試一下 Day 8 的 SelectedPhotos 類別的 select 方法。

it_img_28_1.png
https://dartpad.dev/?id=886bcbddaf299475a141d22e7d099a4e

從這個類別中,我們可以看出 select 有幾個行為,並為其列下測試案例:

  1. 當 photo 未被選擇,也沒超過限制時,就會能成功選擇 photo
  2. 當 photo 被選擇時,呼叫 select 會取消選擇
  3. 當選擇 photo 檔案大小總和超過 250 時,就會丟出 OverLimitException
  4. 當選擇 photo 超過 5 張時,就會丟出 OverLimitException

測試第一個案例

在 Flutter 中,我們會把測試寫在 main 方法中,並在 main 方法中放入單元測試。在單元測試中,透過實現 3A 原則:Arrange、Act、和 Assert,從準備 photo 資料、實際執行 select 方法,最後確認 photo 已經被選擇。當我們完成每一個步驟後,執行測試就從 IDE 看到一個測試通過了。

it_img_28_2.png
https://dartpad.dev/?id=886bcbddaf299475a141d22e7d099a4e

如果讀者們有興趣,也可以自己嘗試第二個測試案例。讓我們跳過第二個,嘗試測試一下非正常的流程。

測試非正常流程

當選擇的總檔案大小超過 250 時,我們執行 select 時,就會拋出一個 OverLimitException。如果我們直接在測試中執行 selectedPhotos.select,測試就會因為 OverLimitException 被拋出而失敗。所以在這測試案例中,我們不能直接呼叫 select 方法。而是必須使用 callback 的方式,把呼叫 select 的工作傳給,expect 方法,並讓 expect 方法幫我們檢查是否 select 方法有正確的拋出 OverLimitException。

it_img_28_3.png
https://dartpad.dev/?id=381128d4a7999a0a086055bd61e28bb1

同樣的,如果讀者們有興趣,也可以自己嘗試第四個測試案例。

設定假資料

有些時候,我們測試目標會相依於其他類別,與其他類別互動,最後完成工作。像是下面例子中 NewsRepository 並不會自己打 API,而是透過 HttpProvider 呼叫。在單元測試中,我們希望能避免直接呼叫真的 API 或使用 DB 等外部資源,我們必須做假這些互動。

it_img_28_4.png

在這些測試中,我們會使用 mock 套件來幫助我們做假這些互動,讓我們更好測試。在 Flutter 中,我們可以選擇使用 mockitomocktail 來幫助我們做假。在這邊,我們使用 mocktail 來示範。

it_img_28_5.png
https://dartpad.dev/?id=73c5804efc8ce14db9b2d7c727ee369e

雖然這個測試與 selectedPhotos 的測試看起來不太一樣,但其實還是符合 3A 原則的。

  1. Arrange:透過 mocktail 的 API 來做假 HttpProvider 的回傳資料
  2. Act:呼叫 NewsRepository.get
  3. Assert:驗證 NewsRepository.get 的回傳資料使否符合預期

測試也需要重構

由於測試方法裡頭充滿了細節,讓我們可能不太好看出測試的流程。所以我們應該也要對測試進行重構,讓測試也具備可讀性。如此一來,當測試失敗時,我們才不會花一大堆時間看懂測試,然後才知道什麼東西出錯了。

it_img_28_6.png
https://dartpad.dev/?id=9fb4a07b2877366e73b2c1401dc051ca

當我們重構測試之後,測試也從長長一串,變短一些,也隱藏一些測試實作的細節,讓我們能更專注在測試的流程上。

當測試很難寫時

有些時候,我們會發現我們很難進行單元測試,有些時候是需要 mock 太多東西,有些時候是為了測試,需要準備很多資料。其實當我們發現測試很難寫時,也可能表示類別的設計有問題,可能是職責太多,也可能是直接使用了靜態外部套件,這些問題都會讓我們測試時遇到很多困難。

所以當我們發現不好測試時,應該適時的檢視當前設計,看看是否應該把類別的職責再拆小一點,或者其他各種方式,提升程式的可測試性。

結論

單元測試看起來雖然簡單,但其實並不容易。我們在這篇文章中,簡單介紹了測試時會需要的東西,並未討論深入討論各種關於單元測試的知識,例如:各種測試替身,和如何設計測試案例 …等。關於這個議題推薦大家去閱讀各路大神的文章,了解更多關於單元測試的知識。


上一篇
Day 27 - 不預期的錯誤
下一篇
Day 29 - 用 Widget Test 測試畫面行為
系列文
關於 Flutter 開發的一些設計雜談30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
月湖 (若虛)
iT邦新手 2 級 ‧ 2022-10-13 00:03:26

以前總會覺得測試只是一個重構時,確保程式行為一致地保護繩。但是越到後來,反而更覺得測試是一個程式思路與品質的照妖鏡,讓我了解這樣寫是不是好的。最近上課,也學到測試也是一個保存當前理解的知識的載體,讓我們知道為什麼要這樣寫。真的是一個很有趣的過程。

我要留言

立即登入留言