iT邦幫忙

2023 iThome 鐵人賽

DAY 3
2
Mobile Development

30 天輕鬆學會 Flutter 測試系列 第 3

Day 3 單元測試不總是那麼容易

  • 分享至 

  • xImage
  •  

昨天介紹了 Dart 單元測試,如何針對一個簡單的類別進行測試。但是在實務上,沒有任何依賴的類別可能並不多,大部分的類別都是會需要與其他類別協作,這些其他類別有可能是專案內的其他類別,也可能是第三方套件裡面包含的類別,今天就來嘗試針對這些類別寫寫測試吧。

UserRepository 測試

假設我們有一個 UserRepository,用以讀取使用者資料,這個 Repository 中有一個非同步的 get 方法。在 get 方法中,程式以非同步方式去使用 http 呼叫遠端 Server 的 API,當 Server 還沒有回應時,我們的程式碼並不會佔用資源,而是會繼續執行其他程式碼,例如更新畫面、或者回應使用者操作。最後當 Server 回應後,才會從 await 的部分繼續往下執行。

class UserRepository {
  Future<User> get(int userId) async {
    var response = await http.get(Uri.parse("https://jsonplaceholder.typicode.com/users/$userId"));
	
    return User.fromJson(jsonDecode(response.body));
  }
}

寫非同步方法的測試跟寫同步方法的測試一樣,都可依照 3A 原則來實踐,唯一稍微不同的呼叫方法,我們在測試中用 async/await 去等待 SUT 的回傳值,寫起來就跟昨天的 Fibonacci 測試幾乎一樣。[範例連結]

main() {
  test("get user ok from api", () async {
    var userRepository = UserRepository();

    var user = await userRepository.get(1);

    expect(user, const User(id: 1, name: "Leanne Graham"));
  });
}

當我們執行測試之後,測試也毫無疑外的成功了,得到了一個綠燈,由於大多時候測試通過的圖示都是綠色的,所以我們常使用綠燈表示測試通過。在上面這個測試中,測試跟正式程式碼一樣,執行到 get(1) 的時候,測試會去呼叫遠端 API 取得 User 1 的資料,最後拿著這個資料與預期結果比較。

1.png

觀眾朋友一樣可以使用 Dartpad 範例來執行,不過由於需要呼叫遠端 API,所以執行時間會稍微需要一點時間,不像 Fibonacci 測試那樣快速,這個部分我們未來會討論。

確保測試能發揮作用

大多時候,如果我們先寫程式,再寫測試,大多時候會直接得到一個綠燈。但是我們可以再多花一些時間,修改一下測試參數,嘗試把測試改壞,讓測試得到一個紅燈,與綠燈相反,因為測試失敗圖示大多為紅色,所以我們也使用紅燈待指測試失敗。

test("get user ok from api", () async {
  ...

  var user = await userRepository.get(2);

  ...
});

以 UserRepository 例子來說,只要我們呼叫 userRepository.get(2) 就會得到一個紅燈。

2.png

那為什麼我們要大費周章的大測試改壞呢?如果我們寫了測試,直接得到一個綠燈,有些時候可能是測試並沒有測到東西,透過輸入錯誤參數來得到錯誤結果,確保未來商業邏輯被改壞時,測試真的能出錯提示我們。一個不會發生錯誤的測試,比完全沒有測試要來得更危險,它會讓我們誤以為我們有測試保護,而放心的重構。但是當我們改壞東西時,這些無用的測試不能及時給我們正確回饋,使我們往錯誤的方向越走越遠。

有問題的單元測試

雖然測試通過了,但是這個測試有點問題,那問題是什麼呢?答案是測試缺乏可重複性,由於測試在執行的過程中,是直接與遠端 Server 進行互動的,當我們的環境缺乏網路,或者遠端 Server 發生問題時,都會造成測試失敗。當測試有一個以上的失敗原因時,就是在提醒我們這個單元測試可能有問題的訊號。

單元測試的特性之一:可重複

單元測試是我們在開發中最頻繁執行的測試,當我們修改了程式碼,可以執行單元測試驗證我們是否有改壞東西,當我們完成需求,也可以執行單元測試驗證需求是否完成。如果執行單元測試時,常常都會因為其他原因造成測試失敗,例如:網路不通或者伺服器掛點,都會降低開發人員使用單元測試的意願,減損單元測試的價值。

想像一下,當有個測試有時好有時壞,壞的原因是因為網路可能當下不通,之前執行個 100 次有 10 次因為網路不通壞掉,當你執行第 101 次壞掉時,你是否會覺得又是網路在搞鬼,而不是程式真的改壞了,然後就直接把程式碼推上去?

單元測試必須能夠重複執行,執行 100 次,100 次都正確通過,避免誤報。當今天測試不通過時,就是真的就是程式碼有問題,這樣才是有效的單元測試。回到我們先前的例子中,由於它需要網路才能執行,可能因為遠端伺服器的好壞造成測試失敗,所以我們必須修改它,使她在每個人的本機上都要能穩定執行。

缺乏可測試性

那我們要怎麼讓這個測試具備可重複性呢?答案是沒辦法,由於程式本身就已經寫死用了 http 這個靜態變數,在完全不修改正式程式碼的情況下,我們是很難解決這個問題。若想檢驗設計是否優秀,我們可以觀察程式的可讀性、可維護性、可靠性 …等方面,而其中也包含了可測試性,優秀的程式碼勢必具備可測試性。

為什麼可測試性很重要呢?當我們能在核心商業上加測試,測試重要的商業邏輯,確保這些邏輯沒有問題,最終反應在產品上的結果就是提高品質。除此之外,測試也能有益於團隊,當程式碼改壞時,也能更快的找到問題。

除了我們的程式需要具備可測試性之外,使用第三方套套件時,選擇有支援測試的套件也是十分重要的,能我們的測試更容易寫,在未來的文章中也會談到。

小結

今天我們嘗試去測試一個呼叫 API 的情境,測試雖然也能通過,但卻不穩定,因為測試容易受到外部環境的影響,讓測試時好時壞,缺乏可重複性。也因為程式本身缺乏可測試性,我們也難以調整測試,讓測試穩定執行。明天我們就來談談如何修改程式與測試,讓程式具備可測試性,也讓測試具備可重複性。


上一篇
Day 2 動手寫一個單元測試
下一篇
Day 4 測試替身與依賴注入
系列文
30 天輕鬆學會 Flutter 測試30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言