概述
- 單元測(cè)試
- Widget測(cè)試
- 集成測(cè)試
一、單元測(cè)試
單元測(cè)試是針對(duì)一個(gè)函數(shù)或者類進(jìn)行測(cè)試
-
1.1、添加測(cè)試依賴
將test或者flutter_test加入依賴文件,默認(rèn)創(chuàng)建的Flutter程序已經(jīng)有了依賴,Test 包提供了編寫測(cè)試所需要的核心功能,在pubspec.yaml里面,dev_dependencies下面的依賴在打包的時(shí)候是會(huì)被忽略掉的dev_dependencies: flutter_test: sdk: flutter -
1.2、創(chuàng)建需要測(cè)試的類
單元測(cè)試通常是測(cè)試一個(gè)函數(shù)或者類,這個(gè)函數(shù)或者類被稱之為是一個(gè)單元。
在這里,我們按照官方示例,創(chuàng)建一個(gè)簡(jiǎn)單的Counter類來演示:class Counter { int value = 0; void increment() => value++; void decrement() => value--; } -
1.3、創(chuàng)建測(cè)試文件
我們?cè)趖est目錄下(注意:不是lib目錄下),創(chuàng)建一個(gè)測(cè)試文件:counter_test.dart通常測(cè)試代碼都會(huì)放在該目錄下,并且測(cè)試文件不會(huì)打包到最終的應(yīng)用程序中;
-
測(cè)試文件通常以 _test.dart 命名,這是 test runner 尋找測(cè)試文件的慣例
import 'package:flutter_test/flutter_test.dart'; import 'package:test_demo/counter.dart'; void main() { test("Counter Class test", () { // 1.創(chuàng)建Counter并且執(zhí)行操作 final counter = Counter(); counter.increment(); // 2.通過expect來監(jiān)測(cè)結(jié)果正確與否 expect(counter.value, 1); }); }
-
1.4、整合多個(gè)測(cè)試
如果對(duì)同一個(gè)類或函數(shù)有多個(gè)測(cè)試,我們希望它們關(guān)聯(lián)在一起進(jìn)行測(cè)試,可以使用groupimport 'dart:math'; import 'package:flutter_test/flutter_test.dart'; import 'package:test_demo/counter.dart'; void main() { group("Counter Test", () { test("Counter Default Value", () { expect(Counter().value, 0); }); test("Counter Increment test", () { final counter = Counter(); counter.increment(); expect(counter.value, 1); }); test("Counter Decrement test", () { final counter = Counter(); counter.decrement(); expect(counter.value, -1); }); }); } -
1.5. 執(zhí)行測(cè)試結(jié)果
用 IntelliJ 或 VSCode 執(zhí)行測(cè)試
IntelliJ 和 VSCode 的 Flutter 插件支持執(zhí)行測(cè)試。用這種方式執(zhí)行測(cè)試是最好的,因?yàn)樗梢蕴峁┳羁斓姆答侀]環(huán),而且還支持?jǐn)帱c(diǎn)調(diào)試。- IntelliJ
- 打開文件 counter_test.dart
- 選擇菜單 Run
- 點(diǎn)擊選項(xiàng) Run 'tests in counter_test.dart'
- 或者,也可以使用系統(tǒng)快捷鍵!
- VSCode
- 打開文件 counter_test.dart
- 選擇菜單 Debug
- 點(diǎn)擊選項(xiàng) Start Debugging
- 或者,也可以使用系統(tǒng)快捷鍵!
在終端執(zhí)行測(cè)試
我們也可以打開終端,在工程根目錄輸入以下命令來執(zhí)行測(cè)試:flutter test test/counter_test.dart - IntelliJ
二、Widget測(cè)試
Widget測(cè)試主要是針對(duì)某一個(gè)封裝的Widget進(jìn)行單獨(dú)測(cè)試
-
2.1、添加測(cè)試依賴
Widget測(cè)試需要先給pubspec.yaml文件的dev_dependencies段添加flutter_test依賴。在單元測(cè)試中我們已經(jīng)說過,默認(rèn)創(chuàng)建的Flutter項(xiàng)目已經(jīng)添加了dev_dependencies: flutter_test: sdk: flutter -
2.2. 創(chuàng)建測(cè)試Widget
import 'package:flutter/material.dart'; class HYKeywords extends StatelessWidget { final List<String> keywords; HYKeywords(this.keywords); @override Widget build(BuildContext context) { return Scaffold( body: ListView( children: keywords.map((key) { return ListTile( leading: Icon(Icons.people), title: Text(key), ); }).toList(), ), ); } } -
2.3、編寫測(cè)試代碼
創(chuàng)建對(duì)應(yīng)的測(cè)試文件,編寫對(duì)應(yīng)的測(cè)試代碼:- testWidgets:flutter_test中用于測(cè)試Widget的函數(shù);
- tester.pumpWidget:pumpWidget 方法會(huì)建立并渲染我們提供的 widget;
- find:find() 方法來創(chuàng)建我們的 Finders;
findsNothing:驗(yàn)證沒有可被查找的 widgets。
findsWidgets:驗(yàn)證一個(gè)或多個(gè) widgets 被找到。
-
findsNWidgets:驗(yàn)證特定數(shù)量的 widgets 被找到。
import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:test_demo/keywords.dart'; void main() { testWidgets("KeywordWidget Test", (WidgetTester tester) async { await tester.pumpWidget(MaterialApp(title: "demo", home: JKKeywords(["abc", "cba", "nba"]),) ); final abcText = find.text("abc"); final cbaText = find.text("cba"); final icons = find.byIcon(Icons.people); expect(abcText, findsOneWidget); expect(cbaText, findsOneWidget); expect(icons, findsNWidgets(2)); }); }官方文檔 中還有更多關(guān)于Widget的測(cè)試
三、集成測(cè)試
單元測(cè)試和Widget測(cè)試都是在測(cè)試獨(dú)立的類或函數(shù)或Widget,它們并不能測(cè)試單獨(dú)的模塊形成的整體或者獲取真實(shí)設(shè)備或模擬器上應(yīng)用運(yùn)行的狀態(tài);
這些測(cè)試任務(wù)可以交給 集成測(cè)試 來完成;
集成測(cè)試 需要有兩個(gè)大的步驟:1、發(fā)布一個(gè)可測(cè)試應(yīng)用程序到真實(shí)設(shè)備或者模擬器上;2、利用獨(dú)立的測(cè)試套件去驅(qū)動(dòng)應(yīng)用程序,檢查儀器是否完好可用;
-
3.1、創(chuàng)建可測(cè)試應(yīng)用程序
我們需要?jiǎng)?chuàng)建一個(gè)可以運(yùn)行在模擬器或者真實(shí)設(shè)備的應(yīng)用程序。
這里我直接使用了官方的示例程序,但是不同的是我這里給兩個(gè)Widget添加了兩個(gè)Key顯示數(shù)字的
Text Widget:ValueKey("counter")-
點(diǎn)擊按鈕的
FloatingActionButton Widget:key: ValueKey("increment")import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', key: ValueKey("counter"), style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( key: ValueKey("increment"), onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
-
3.2. 添加flutter_driver依賴
我們需要用到flutter_driver包來編寫 集成測(cè)試,所以我們需要把flutter_driver依賴添加到應(yīng)用pubspec.yaml文件的dev_dependencies位置:dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter test: any -
3.3. 創(chuàng)建測(cè)試文件
和單元測(cè)試以及Widget測(cè)試不同的是,集成測(cè)試的程序和待測(cè)試的應(yīng)用并不在同一個(gè)進(jìn)程內(nèi),所以我們通常會(huì)創(chuàng)建兩個(gè)文件:- 文件一:用于啟動(dòng)帶測(cè)試的應(yīng)用程序
- 文件二:編寫測(cè)試的代碼
我們可以將這兩個(gè)文件放到一個(gè)文件中:test_driver,目錄結(jié)構(gòu)如下
-
3.4. 編寫安裝應(yīng)用代碼
安裝應(yīng)用程序代碼在app.dart中,分層兩步完成:- 讓
flutter driver的擴(kuò)展可用 - 運(yùn)行應(yīng)用程序
test_driver/app.dart文件中增加以下代碼:import 'package:flutter_driver/driver_extension.dart'; import 'package:test_demo/main.dart' as app; void main() { // 開啟DriverExtension enableFlutterDriverExtension(); // 手動(dòng)調(diào)用main函數(shù), 啟動(dòng)應(yīng)用程序 app.main(); } - 讓
-
3.5. 編寫集成測(cè)試代碼
現(xiàn)在我們有了待測(cè)應(yīng)用,我們可以為它編寫測(cè)試文件了。這包含了四個(gè)步驟:- 創(chuàng)建 SerializableFinders 定位指定組件
- 在 setUpAll() 函數(shù)中運(yùn)行測(cè)試案例前,先與待測(cè)應(yīng)用建立連接
- 測(cè)試重要場(chǎng)景
- 完成測(cè)試后,在 teardownAll() 函數(shù)中與待測(cè)應(yīng)用斷開連接
test_driver/app_test.dart文件中增加以下代碼:import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; void main() { group("Counter App Test", () { FlutterDriver driver; // 初始化操作 setUpAll(() async { driver = await FlutterDriver.connect(); }); // 測(cè)試結(jié)束操作 tearDownAll(() { if (driver != null) { driver.close(); } }); // 編寫測(cè)試代碼 final counterTextFinder = find.byValueKey('counter'); final buttonFinder = find.byValueKey('increment'); test("starts at 0", () async { expect(await driver.getText(counterTextFinder), "0"); }); test("on tap click", () async { await driver.tap(buttonFinder); expect(await driver.getText(counterTextFinder), "1"); }); }); } -
3.6. 運(yùn)行集成測(cè)試
首先,啟動(dòng)安卓模擬器或者 iOS 模擬器,或者直接把 iOS 或 Android 真機(jī)連接到你的電腦上。
接著,在項(xiàng)目的根文件夾下運(yùn)行下面的命令flutter drive --target=test_driver/app.dart- 這個(gè)指令的作用:
- 創(chuàng)建 --target 目標(biāo)應(yīng)用并且把它安裝在模擬器或真機(jī)中
- 啟動(dòng)應(yīng)用程序
- 運(yùn)行位于 test_driver/ 文件夾下的 app_test.dart 測(cè)試套件
運(yùn)行結(jié)果:我們會(huì)發(fā)現(xiàn)正常運(yùn)行,并且結(jié)果app中的FloatingActionButton自動(dòng)被點(diǎn)擊了一次
- 這個(gè)指令的作用:
