應(yīng)用的功能越多,手動(dòng)測(cè)試的難度就越大。一套完整的自動(dòng)化測(cè)試將幫助您確保您的應(yīng)用在發(fā)布之前正確執(zhí)行,同時(shí)保留您的功能和錯(cuò)誤修復(fù)速度。
有很多種自動(dòng)化測(cè)試。這些總結(jié)如下:
這里是一個(gè)表格,總結(jié)了在不同類型測(cè)試之間進(jìn)行選擇的權(quán)衡:
單元測(cè)試 | widget測(cè)試 | 集成測(cè)試 | |
---|---|---|---|
Confidence | Low | Higher | Highest |
維護(hù)成本 | Low | Higher | Highest |
依賴 | Few | More | Lots |
執(zhí)行速度 | Quick | Slower | Slowest |
提示: 作為一個(gè)經(jīng)驗(yàn)法則,經(jīng)過充分測(cè)試的應(yīng)用程序具有非常多的單元和widget測(cè)試,通過代碼覆蓋(code coverage)進(jìn)行跟蹤,以及覆蓋所有重要使用場(chǎng)景的大量集成測(cè)試。
某些Flutter庫,如dart:ui在獨(dú)立的Dart VM附帶的Dart SDK的中是不可用。該flutter test命令允許您在本地Dart VM中運(yùn)行測(cè)試,使用無頭版(不會(huì)顯示UI)的Flutter引擎。 使用這個(gè)命令你可以運(yùn)行任何測(cè)試,不管它是否依賴于Flutter的庫。
使用package:test,編寫一個(gè)Flutter單元測(cè)試。編寫單元測(cè)試使用的package:test文檔在這里。
例如:
將此文件添加到 test/unit_test.dart:
import 'package:test/test.dart';
void main() {
test('my first unit test', () {
var answer = 42;
expect(answer, 42);
});
}
另外,您必須將以下內(nèi)容添加到您的pubspec.yaml:
dev_dependencies:
flutter_test:
sdk: flutter
即使你的測(cè)試本身沒有明確導(dǎo)入flutter_test,也需要這樣做 ,因?yàn)闇y(cè)試框架本身在后臺(tái)也使用了它。
要運(yùn)行測(cè)試,從您的項(xiàng)目目錄(而不是從test子目錄)運(yùn)行 flutter test test/unit_test.dart
要運(yùn)行所有測(cè)試,請(qǐng)從項(xiàng)目目錄運(yùn)行flutter test
您以類似于單元測(cè)試的方式實(shí)現(xiàn)widget測(cè)試。要在測(cè)試中執(zhí)行與widget的交互,請(qǐng)使用Flutter提供的WidgetTester。 例如,您可以發(fā)送點(diǎn)擊和滾動(dòng)手勢(shì)。您還可以使用WidgetTester在widget樹中查找子widget、讀取文本、驗(yàn)證widget屬性的值是否正確。
例子:
將此文件添加到test/widget_test.dart:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('my first widget test', (WidgetTester tester) async {
// You can use keys to locate the widget you need to test
var sliderKey = new UniqueKey();
var value = 0.0;
// Tells the tester to build a UI based on the widget tree passed to it
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new MaterialApp(
home: new Material(
child: new Center(
child: new Slider(
key: sliderKey,
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
),
),
),
);
},
),
);
expect(value, equals(0.0));
// Taps on the widget found by key
await tester.tap(find.byKey(sliderKey));
// Verifies that the widget updated the value correctly
expect(value, equals(0.5));
});
}
運(yùn)行 flutter test test/widget_test.dart.
查看所有可用于widget測(cè)試的package:flutter_test API
為了幫助調(diào)試widget測(cè)試,您可以使用debugDumpApp() 函數(shù)來可視化測(cè)試的UI狀態(tài), 或者只是簡(jiǎn)單的在您的首選運(yùn)行時(shí)環(huán)境(例如模擬器或設(shè)備)中運(yùn)行flutter run test/widget_test.dart以查看您的測(cè)試運(yùn)行。 在運(yùn)行flutter run的測(cè)試的會(huì)話期間,您還可以交互式地點(diǎn)擊Flutter工具的部分屏幕來打印建議的Finder。
如果您熟悉Selenium/WebDriver(web),Espresso(Android)或UI Automation(iOS),那么Flutter Driver就是Flutter與這些集成測(cè)試工具的等價(jià)物。 此外,F(xiàn)lutter Driver還提供API以記錄測(cè)試執(zhí)行的操作的性能跟蹤(又名時(shí)間軸)。
Flutter的Driver是:
這兩者允許你:
要使用flutter_driver,您必須將以下塊添加到您的pubspec.yaml:
dev_dependencies:
flutter_driver:
sdk: flutter
一個(gè)指令化的應(yīng)用程序是一個(gè)Flutter應(yīng)用程序,它啟用了Flutter Driver 擴(kuò)展。啟用擴(kuò)展請(qǐng)調(diào)用enableFlutterDriverExtension()。
例:
假設(shè)你有一個(gè)入口點(diǎn)的應(yīng)用程序my_app/lib/main.dart。要?jiǎng)?chuàng)建它的指令化版本,請(qǐng)?jiān)趍y_app/test_driver/下創(chuàng)建一個(gè)Dart文件。 在您正在測(cè)試的功能之后命名它; 接下來定位到my_app/test_driver/user_list_scrolling.dart:
// 這一行導(dǎo)入擴(kuò)展
import 'package:flutter_driver/driver_extension.dart';
void main() {
// 啟用擴(kuò)展
enableFlutterDriverExtension();
// Call the `main()` of your app or call `runApp` with whatever widget
// you are interested in testing.
}
集成測(cè)試是一個(gè)簡(jiǎn)單的package:test測(cè)試,它使用Flutter Driver API告訴應(yīng)用程序執(zhí)行什么操作,然后驗(yàn)證應(yīng)用程序是否執(zhí)行了此操作。
例子:
為了有意思起見,我們也讓我們的測(cè)試記錄下性能跟蹤(performance timeline)。我們創(chuàng)建一個(gè)user_list_scrolling_test.dart測(cè)試文件位于my_app/test_driver/下:
import 'dart:async';
// Imports the Flutter Driver API
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
group('scrolling performance test', () {
FlutterDriver driver;
setUpAll(() async {
// 連接app
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
// 關(guān)閉連接
driver.close();
}
});
test('measure', () async {
// 記錄閉包中的performance timeline
Timeline timeline = await driver.traceAction(() async {
// Find the scrollable user list
SerializableFinder userList = find.byValueKey('user-list');
// Scroll down 5 times
for (int i = 0; i < 5; i++) {
// Scroll 300 pixels down, for 300 millis
await driver.scroll(
userList, 0.0, -300.0, new Duration(milliseconds: 300));
// Emulate a user's finger taking its time to go back to the original
// position before the next scroll
await new Future<Null>.delayed(new Duration(milliseconds: 500));
}
// Scroll up 5 times
for (int i = 0; i < 5; i++) {
await driver.scroll(
userList, 0.0, 300.0, new Duration(milliseconds: 300));
await new Future<Null>.delayed(new Duration(milliseconds: 500));
}
});
// The `timeline` object contains all the performance data recorded during
// the scrolling session. It can be digested into a handful of useful
// aggregate numbers, such as "average frame build time".
TimelineSummary summary = new TimelineSummary.summarize(timeline);
summary.writeSummaryToFile('stocks_scroll_perf', pretty: true);
summary.writeTimelineToFile('stocks_scroll_perf', pretty: true);
});
});
}
要在Android設(shè)備上運(yùn)行測(cè)試,請(qǐng)通過USB將設(shè)備連接到計(jì)算機(jī)并啟用USB調(diào)試。然后運(yùn)行以下命令:
flutter drive --target=my_app/test_driver/user_list_scrolling.dart
該命令將:
您可能想知道該命令如何找到正確的測(cè)試文件。flutter drive 命令使用一種約定來查找與--target應(yīng)用程序在同一目錄中具有相同文件名但是具有_test后綴的測(cè)試文件。
更多建議: