在開發 Flutter 程式的過程中,或多或少都會使用圖片或 Icon 來增加使用者體驗,畢竟人都是視覺動物,比起文字,豐富的圖片與動畫特效更加吸人眼球。今天就來看看在 Widget Test 中,在測試圖片時會遇到什麼問題吧。一樣讓我們先舉個簡單的例子。
我們來看個簡單的程式,在程式中用圖片呈現葉問 2 的經典場景,畫面一開始顯示了葉問叫洪師傅切對手中路,然後使用者畫面中的按鈕看看洪師父怎麼回應。[範例程式]
那我們想驗證假設我想驗證按鈕的功能是否正常要怎麼測呢?想必觀眾朋友應該都已經熟練了,我們可以驗證洪師父的圖片有沒有有沒有出現在畫面上。
main() {
testWidgets("show master hong's reply", (tester) async {
await tester.pumpWidget(const IpMan());
await tester.tap(find.text("看看洪師父怎麼說"));
// 驗證洪師父圖片
});
}
來到 3A 的最後一步,我們要怎麼驗證圖片呢?如果我們搜尋 Finder,可以找到 find.image 這個 Finder,看起來應該就是要用它了,再看看他的參數是 ImageProvider,如果我們再移至 ImageProvider 的定義,用 IDE 找到繼承 ImageProvider,就能找到一係列可能選擇。
在範例中,我們使用 Image.asset() 來顯示圖片,相對應的在測試中的我們就得使用 AssetImage 了。
main() {
testWidgets("show master hong's reply", (tester) async {
await tester.pumpWidget(const IpMan());
await tester.tap(find.text("看看洪師父怎麼說"));
await tester.pump();
expect(find.image(const AssetImage("assets/master_hong.jpg")), findsOneWidget);
});
}
最後我們執行測試,得到綠燈。
在上面的測試中,我們在正式程式碼與測試中都是手動寫死了圖片路徑,萬一兩邊寫的不相符,我們可能一時間可能檢查不出問題。所以我們可以使用 flutter_gen 來產生這些路徑的常數,預設產生出來的常數檔放在 lib/gen 目錄中,檔名為 Assets。
然後我們就能直接在正式與測試程式碼中使用這些常數,避免打錯字的問題。
testWidgets("show master hong's reply", (tester) async {
await tester.pumpWidget(const IpMan());
await tester.tap(find.text("看看洪師父怎麼說"));
await tester.pump();
expect(find.image(AssetImage(Assets.masterHong.path)), findsOneWidget);
});
Flutter 的 Image 有許多 factory 方法,在上面的例子中,我們使用過 Image.asset(),而在眾多方法中,還有一個也是我們常用的,那就是 Image.netowrk()。在這個方法中,我們把遠端圖片網址當參數放到 Image.network() 中,在 Image 要顯示在畫面上時,Flutter 就會幫我們去讀取遠端圖片並顯示在畫面上。讓我們修改一下上面的例子,都改成使用 Image.network() 在來測試看看會發什麼事吧。[範例程式]
修改完成之後,我們測試也跟著做相應的調整,之前我們查看 ImageProvider 的子類的時候,也有看到 NetworkImage 這個類別,剛好可以用在測試上。
testWidgets("show master hong's reply", (tester) async {
await tester.pumpWidget(const IpMan());
await tester.tap(find.text("看看洪師父怎麼說"));
await tester.pump();
expect(find.image(const NetworkImage("https://raw.githubusercontent.com/easylive1989/images/master/static/images/2023IThome/Day20/master_hong.jpg")), findsOneWidget);
});
當我們改完測試並執行後會發現,測試得到了紅燈,查看錯訊息會發現,測試回報了讀取網路圖片失敗,無法正確載入葉問的圖片。
還記得我們在之前的文章中有提到,在 Widget Test 預設是會擋掉所有遠端呼叫的,讀取遠端圖片也是一種遠端呼叫,所以自然的也行不通。那我們要怎麼處理呢?
在 Image.network() 的參數中,有一個 errorBuilder 參數,如果我們有設定參數,當圖片載入失敗時,就會呼叫 errorBuilder 顯示圖片載入失敗的畫面。
Image.network(
"https://raw.githubusercontent.com/easylive1989/images/master/static/images/2023IThome/Day20/ipman.jpg",
errorBuilder: (context, error, stackTrace) => const Text("圖片載入失敗"),
)
如果們修改一下正式程式碼,在使用 Image.network() 時都加上 errorBuilder,當圖片無法載入時,也能正常顯示 Widget,測試也就能正常通過。[範例程式]
那我們還有沒有其他方式處理 Widget Test 的錯誤呢?
我們除了使用 errorBuilder 之外,還能使用 network_image_mock 套件,這個套件能協助我們避免因為 Image.network() 讀不到圖片而造成 Widget Test 報錯。只要在呼叫 pumpWidget 時,用 mockNetworkImages 包住即可。
testWidgets("show master hong's reply", (tester) async {
await mockNetworkImages(() => tester.pumpWidget(const IpMan()));
await tester.tap(find.text("看看洪師父怎麼說"));
await tester.pump();
expect(find.image(const NetworkImage("https://raw.githubusercontent.com/easylive1989/images/master/static/images/2023IThome/Day20/master_hong.jpg")), findsOneWidget);
});
其實大多時候,我們都不會驗證圖片作為結果,就像開頭講的,圖片大多時候只是增加使用者體驗,而非重要的資訊。既然這樣,我們在選擇驗證的什麼時候,應該是要選擇先驗證那些畫面上的重要資訊,只有當不得已的情況,畫面中只有圖片拿來做驗證的時候,我們才會選擇用圖片驗證。
假設在建立聊天室的例子中,我們建立成功的時候不是顯示訊息,而是顯示一張圖時,若我們想從畫面檢查是否有出現成功訊息,那就只能檢查圖片了。
今天介紹了跟圖片相關的議題,在驗證圖片時,我們可以使用 find.image,並根據正式程式碼使用的圖片載入方式,決定在測試中使用哪種 ImageProvider。我們也討論如何處理 Image.network() 在 Widget Test 中的錯誤與什麼時候需要驗證圖片,希望大家看完今天的文章都能都有一點收穫。
嗨,保羅哥
圖片網址訪問的結果是 AccessDenied,我看不到你文章的圖片。
哎呀,忘記更新圖片,感謝提醒