iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
Mobile Development

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

Day 13 Widget Test 是什麼?

  • 分享至 

  • xImage
  •  

今天我們要從單元測試進入 Widget Test 的部分了,我們花了十幾天的時間介紹 Dart 的單元測試,也介紹了許多測試相關的概念與技巧,單元測試是最容易寫的測試,其中的概念多多少少也可以運用在 Widget Test 或其他類型測試中,讓我們直接開始介紹 Widget Test 吧。

什麼是 Widget Test

Flutter 官方文件中介紹 Widget Test 是一種 Component Test,透過模擬使用者操作 UI 的行為,然後驗證畫面結果是否符合預期,有點像是 End to End 測試。在 Widget Test 中,雖然測試會模擬使用者的操作畫面,但實際上在執行 Widget Test 並不會真的看到畫面,也不會真的去打遠端 Server 的 API,所以 Widget Test 的執行速度十分接近單元測試。

Unit Widget Integration
Confidence Low Higher Highest
Maintenance cost Low Higher Highest
Dependencies Few More Most
Execution speed Quick Quick Slow

出處:https://docs.flutter.dev/testing/overview

為什麼要寫 Widget Test

Flutter 是一個 UI 框架,在開發的時候,假設架構使用 Clean Architecture,我們雖然可以把邏輯都封裝到 Adapter 層或 Use Case 層,甚至 Entity 層,但是 UI 層還是多少會存在著操作 Adapter 層 API 的整合邏輯。此時,使用 Widget Test 來測試,才能測試到這些整合邏輯,也會比單元測試要來的接近實際情況。

那Widget Test 是不是可以取代單元測試呢?答案顯然不是,Widget Test 看起來美好,實際繼上還是有許多不方便的地方,像是除錯比較不方便,或者隨著測試的越外層,測試需要準備的資料也越多,寫起測試來肯定不像單元測試那樣順暢,維護比較麻煩。但是其實這些問題,隨著我們的持續增加我們的測試經驗與技巧,再加上善用 IDE 工具,還是能減少撰寫 Widget Test 的時間,降低開發成本。

最簡單的例子

在我們用 flutter 建新專案時,裡頭預設就會包含一個簡單的例子與 Widget Test,讓我們來看看這個簡單的例子。

1.png

在這個簡單的例子中,每當使用者按一次按鈕,畫面中的數字就會加 1。在範例測試中,也是依循著這個邏輯。

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    await tester.pumpWidget(const MyApp());

    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}

與單元測試類似,我們一樣是把 Widget Test 的測試案例放在 main 方法中,但是與單元測試不一樣的是,Widget Test 使用 testWidgets 方法來測試,而不是 test。在 testWidgets 的第二個參數會傳入非同步方法,這個非同步方法中有一個 WidgetTester 的參數,這個 WidgetTester 就是我們主要拿來與 Widget 互動的工具。

與單元測試建立 SUT 類似,我們在 Widget Test 中,需要決定要測試哪個 Widget,然後用 WidgetTester.pumpWidget 將 Widget 畫出來。

await tester.pumpWidget(const MyApp());

接著就可以用 WidgetTester 的 tap 方法模擬使用者點擊 Icon,再 tap 方法中,我們需要使用 Finder 幫我們找出 Icon,把他傳入 tap 方法中,讓程式執行點擊後的動作,使 count 加 1。

await tester.tap(find.byIcon(Icons.add));

雖然我們成功地把 count + 1,但是在測試中的畫面還是維持 0,因為 Widget Test 並不會自己刷新畫面,需要我們呼叫 WidgetTester.pump 方法,主動通知 WidgetTester 刷新畫面。

await tester.pump();

最後我們就可以用 expect 來驗證畫面數字是不是變成 1 了

expect(find.text('1'), findsOneWidget);

上面這個例子是十分簡單的 Widget Test 範例,在未來的幾天文章中,我們會介紹如何 Finder 找出各種不同的 Widget 與如何模擬各種使用者操作,透過組合 Finder 與 WidgetTester ,我們就能模擬大部分的情境了。

單元測試 vs Widget Test

  • 一樣是 3A 原則

Widget Test 與單元測試在測試的結構上,兩者並沒有多大差別。在單元測試中,我們思考的是要怎麼測試 SUT 的行為,呼叫哪些方法,驗證哪些狀態。來到 Widget Test,我們則是在思考要測試哪個畫面的行為,點擊哪些按鈕,驗證畫面上出現哪些元素,本質上一樣是 3A 原則。

  • 測試的角度不同

寫單元測試時,只要熟悉單元測試的概念,即便不熟悉 Dart 語法或 API,我們寫起來也不會有太大問題,因為單元測試是測試邏輯,對於語言的依賴度不大。但是寫 Widget Test 時,我們測試的角度就是從畫面出發,思考使用者與畫面如何互動,最後在畫面上產生何種結果,這就與單元測試有很大不同。

  • 需要學會的測試 API 變多了

在單元測試中,我們使用的測試 API 基本上只有 expect 與測試替身,但是在 Widget Test 中,我們除了單元測試會用到的 API 之外,我們還要了解如何使用 Finder 找到想要找的 Widget,也要會使用 WidgetTester 的各種 API 模擬使用者操作,學習成本上多了不少。

小結

今天是介紹 Widget Test 的第一天,在開發 Flutter 程式的過程中,如果只有使用單元測試,一個完整的使用者行為,可能就會被拆分成好幾段段不同測試,適時使用 Widget Test 反而會比較好測試,維護測試簡單一點。


上一篇
Day 12 單元測試回顧
下一篇
Day 14 Finder 與他們的驗證方式
系列文
30 天輕鬆學會 Flutter 測試30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言