上回書對 Flutter 中 Widget 測試的官方 Demo 進行了簡單的講解,這篇文章我們對自己的專案進行 Widget 測試。
就拿 「想吃啥」APP 來進行測試吧。
在首頁中,我們可以看到有 6 個 Widget,有:
- 葷菜 & 素菜:
- 選個菜吧 ×2
- Button ×2
因為平時我們寫APP的時候,肯定會封裝一些 Widget 來進行復用,所以首頁中 選個菜吧 & Button 都被我封裝成了一個 Widget。
下面我們就先針對這兩個 Widget 進行測試。
選個菜吧
首先我們也是根據步驟來,先編寫 Widget,然後編寫 Widget 測試。(說得好像是廢話?)
編寫Widget
其中 「選個菜吧」是封裝了一個 Row
,裡面包含了一個 Text
& IconButton
,具體程式碼如下:
class MenuWidget extends StatelessWidget {
final Stream stream;
final VoidCallback callback;
MenuWidget(this.stream, this.callback);
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
StreamBuilder(
stream: stream,
initialData: "選個菜吧",
builder: (context, snapshot) {
return Text(
snapshot.data,
style: TextStyle(fontSize: 34, color: Colors.black87),
);
},
),
IconButton(
icon: Icon(Icons.refresh),
onPressed: callback,
),
],
);
}
}
複製程式碼
引數有兩個:
- Stream:用於
StreamBuilder
的 stream 引數,在本APP中是用來隨機選單 - VoidCallback:用於
IconButton
的點選事件
這樣我們就封裝成了一個 Widget,可以在編寫 UI 的時候複用了,那既然寫完了 Widget,下面就要對他進行測試了。
編寫 Widget 測試
在編寫測試程式碼之前,我們要明確該 Widget 的作用。(由於我是在寫文章,可能很多人沒仔細看前面的程式碼,所以這裡還是解釋一下該 Widget的邏輯):
- 該 Widget 是由兩個 Widget 組合而成。一個是 Text, 一個是 IconButton。
- Text 用來展示菜譜。
- IconButton 用來點選呼叫隨機菜譜的方法。
所以我們在寫測試的時候,也應該按照上述的邏輯來寫,我寫的測試程式碼如下:
void main() {
testWidgets('MenuWidget test', (WidgetTester tester) async {
StreamController<String> _controller = StreamController();
try {
await tester.pumpWidget(MaterialApp(
home: Material(
child: MenuWidget(
_controller.stream,
() => _controller.sink.add("111"),
),
),
));
expect(find.text('選個菜吧'), findsOneWidget);
await tester.tap(find.byIcon(Icons.refresh));
await tester.pump();
expect(find.text('111'), findsOneWidget);
} finally {
_controller.close();
}
});
}
複製程式碼
上述邏輯大致如下:
- 建立測試的 Widget,也就是我們的
MenuWidget
,其中需要兩個引數,一個是 stream,一個是VoidCallback
。 - 由於 Stream 必須要 close,所以套了一層異常捕獲,在
finally
中釋放 stream。 - 定義好 Widget 後,查詢,是否有「選個菜吧」 的Widget。
- 如果有的話,則點選一個 Icon 為
Icons.refresh
的按鈕。 - 然後重新整理頁面
- 查詢是否有文字為「111」的 Widget。
執行測試程式碼,結果如下:
可以看到確實是通過了,那就證明,我們組合的這個 Widget 不存在邏輯的問題,也就是該 Widget 可用。
Button
在開發中,對於 Button
樣式的一致性大家肯定是有了解的,那既然如此,就要封裝好一個通用的Button。
編寫Widget
一個 Button
需要的引數無非也就兩個:
- Button 上顯示的文案
- Button 點選回撥
程式碼如下:
class CommonButton extends StatelessWidget {
final String text;
final VoidCallback callback;
CommonButton(this.text, this.callback);
@override
Widget build(BuildContext context) {
return RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
color: Theme.of(context).accentColor,
textColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 50, vertical: 10),
onPressed: callback,
child: Text(
text,
style: TextStyle(fontSize: 18),
),
);
}
}
複製程式碼
這裡封裝的 Button 就是一個 RaisedButton
,用了圓角,設定了文字的大小,這樣就封裝好了。
下面就寫該 Button 的測試。
編寫 Widget 測試
Button 的測試邏輯還是非常簡單的,我們只需要判斷:
- 文字是否正常顯示
- 點選回撥是否走得通
以上兩個條件就 ok 了。
測試程式碼如下:
testWidgets('CommonButton test', (WidgetTester tester) async {
bool flag = false;
await tester.pumpWidget(MaterialApp(
home: Material(
child: CommonButton(
'我是CommonButton',
() => flag = true,
),
),
));
expect(find.text('我是CommonButton'), findsOneWidget);
expect(flag, isFalse);
await tester.tap(find.byType(CommonButton));
expect(flag, isTrue);
});
複製程式碼
上述程式碼邏輯為:
- 定義一個 flag,用來測試 Button 的點選回撥
- 建立 Widget,定義好引數
- 判斷 Button 的文案是否正常顯示
- 判斷 flag 是否為開始定義的值
- 點選該 Button
- 判斷 flag 是否已經更改
執行結果如下:
可以看到上述有兩個 test 都完成了。
總結
在 Flutter 中,一切皆為 Widget。
相信各位學 Flutter 的也都知道這個概念,那就可以看得出來,Widget 測試是 Flutter 中最重要的測試。
一個好的程式,測試覆蓋率應該也要很高。