Ich denke, viele Leute sind mit Flutter bereits vertraut und haben, zumindest aus Interesse, einfache Anwendungen darauf gestartet. Es ist an der Zeit sicherzustellen, dass alles so funktioniert, wie es benötigt wird, und Integrationstests helfen uns dabei.

Integrationstests für Flutter werden mit dem Flutter-Treiber geschrieben, für den es auf der offiziellen Website ein einfaches und verständliches Tutorial gibt . Solche Tests ähneln in ihrer Struktur Espresso aus der Android-Welt. Zuerst müssen Sie die UI-Elemente auf dem Bildschirm finden:
final SerializableFinder button = find.byValueKey("button");
Führen Sie dann einige Aktionen mit ihnen aus:
driver = await FlutterDriver.connect(); ... await driver.tap(button);
und vergewissern Sie sich, dass die erforderlichen Benutzeroberflächenelemente den gewünschten Status haben:
final SerializableFinder text = find.byValueKey("text"); expect(await driver.getText(text), "some text");
Mit einem einfachen Beispiel sieht natürlich alles elementar aus. Angesichts des Wachstums der getesteten Anwendung und der zunehmenden Anzahl von Tests möchte ich die Suche nach Benutzeroberflächenelementen jedoch nicht vor jedem Test wiederholen. Außerdem müssen Sie diese UI-Elemente strukturieren, da es viele Bildschirme geben kann. Machen Sie es sich dazu bequemer, Tests zu schreiben.
Bildschirmobjekte
In Android ( Kakao ) wird dieses Problem durch Gruppieren von Benutzeroberflächenelementen von jedem Bildschirm in Bildschirm ( Seitenobjekt ) gelöst. Ein ähnlicher Ansatz kann hier angewendet werden, mit der Ausnahme, dass Sie in Flutter zum Ausführen von Aktionen mit UI-Elementen nicht nur Finder
(zum Suchen nach einem UI-Element), sondern auch FlutterDriver
(zum Ausführen einer Aktion) benötigen, sodass Sie einen Link zu FlutterDriver
in speichern FlutterDriver
Screen
.
Um jedes UI-Element zu definieren, fügen wir die DWidget
Klasse hinzu (D - in diesem Fall vom Wort Dart). Um ein DWidget
zu erstellen DWidget
benötigen Sie einen FlutterDriver
, mit dem Aktionen auf diesem UI-Element ausgeführt werden, sowie einen ValueKey
, der mit dem ValueKey
Flutter des Widgets aus der Anwendung übereinstimmt, mit der wir interagieren möchten:
class DWidget { final FlutterDriver _driver; final SerializableFinder _finder; DWidget(this._driver, dynamic valueKey) : _finder = find.byValueKey(valueKey); ...
find.byValueKey(…)
beim manuellen Erstellen jedes DWidget
unpraktisch. DWidget
daher den ValueKey
Wert ValueKey
an den ValueKey
, und das DWidget
selbst erhält den gewünschten SerializableFinder
. Es ist auch nicht sehr praktisch, FlutterDriver
beim Erstellen jedes DWidget
manuell zu übergeben. Sie können FlutterDriver
in BaseScreen
speichern FlutterDriver
in BaseScreen
übertragen und DWidget
eine neue Methode BaseScreen
, um BaseScreen
zu erstellen:
abstract class BaseScreen { final FlutterDriver _driver; BaseScreen(this._driver); DWidget dWidget(dynamic key) => DWidget(_driver, key); ...
So wird das Erstellen von Screens-Klassen und das Abrufen von UI-Elementen viel einfacher:
class MainScreen extends BaseScreen { MainScreen(FlutterDriver driver) : super(driver); DWidget get button => dWidget('button'); DWidget get textField => dWidget('text_field'); ... }
Das Warten loswerden
Eine weitere FlutterDriver
Sache beim Schreiben von Tests mit FlutterDriver
ist die Notwendigkeit, vor jeder Aktion eine FlutterDriver
hinzuzufügen:
await driver.tap(button); await driver.scrollUntilVisible(list, checkBox); await driver.tap(checkBox); await driver.tap(text); await driver.enterText("some text");
Das Vergessen des await
ist einfach, und ohne dies funktionieren die Tests nicht ordnungsgemäß, da driver
Future<void>
Wenn sie ohne await
aufgerufen werden await
bis zum ersten await
in der Methode ausgeführt und der Rest der Methode wird auf später verschoben.
Sie können dies TestAction
indem Sie eine TestAction
, die Future
" TestAction
", sodass wir warten können, bis eine Aktion abgeschlossen ist, bevor wir zur nächsten TestAction
:
typedef TestAction = Future<void> Function();
( TestAction
ist im Wesentlichen jede Funktion (oder Lambda), die eine Future<void>
zurückgibt.)
Jetzt können Sie die TestAction
Sequenz ganz einfach TestAction
, ohne unnötig warten zu müssen:
Future<void> runTestActions(Iterable<TestAction> actions) async { for (final action in actions) { await action(); } }
DWidget
zur Interaktion mit UI-Elementen verwendet. Es ist sehr praktisch, wenn diese Aktionen TestAction
damit sie in der runTestAction
Methode verwendet werden können. Zu diesem DWidget
verfügt die DWidget
Klasse über folgende Aktionsmethoden:
class DWidget { final FlutterDriver _driver; final SerializableFinder _finder; ... TestAction tap({Duration timeout}) => () => _driver.tap(_finder, timeout: timeout); TestAction setText(String text, {Duration timeout}) => () async { await _driver.tap(_finder, timeout: timeout); await _driver.enterText(text ?? "", timeout: timeout); }; ... }
Jetzt können Sie Tests wie folgt schreiben:
class MainScreen extends BaseScreen { MainScreen(FlutterDriver driver) : super(driver); DWidget get field_1 => dWidget('field_1'); DWidget get field_2 => dWidget('field_2'); DWidget field2Variant(int i) => dWidget('variant_$i'); DWidget get result => dWidget('result'); } … final mainScreen = MainScreen(driver); await runTestActions([ mainScreen.result.hasText("summa = 0"), mainScreen.field_1.setText("3"), mainScreen.field_2.tap(), mainScreen.field2Variant(2).tap(), mainScreen.result.hasText("summa = 5"), ]);
Wenn Sie in runTestActions
eine Aktion runTestActions
, die nicht mit DWidget
, müssen Sie nur ein Lambda erstellen, das ein Future<void>
DWidget
:
await runTestActions([ mainScreen.result.hasText("summa = 0"), () => driver.requestData("some_message"), () async => print("some_text"), mainScreen.field_1.setText("3"), ]);
FlutterDriverHelper
FlutterDriver
bietet verschiedene Methoden für die Interaktion mit FlutterDriver
(Drücken, Empfangen und Eingeben von Text, Scrollen usw.). Für diese Methoden verfügt DWidget
über entsprechende Methoden, die TestAction
.
Der FlutterDriverHelper
halber wird der FlutterDriverHelper
in diesem Artikel beschriebene Code als FlutterDriverHelper
Bibliothek auf pub.dev veröffentlicht .
Um durch Listen zu scrollen, in denen Elemente dynamisch erstellt werden (z. B. ListView.builder
), verfügt FlutterDriver
über eine scrollUntilVisible
Methode:
Future<void> scrollUntilVisible( SerializableFinder scrollable, SerializableFinder item, { double alignment = 0.0, double dxScroll = 0.0, double dyScroll = 0.0, Duration timeout, }) async { ... }
Diese Methode scrollt das scrollbare Widget in die angegebene Richtung, bis das item
Widget auf dem Bildschirm angezeigt wird (oder bis eine timeout
). Um nicht bei jedem Bildlauf einen DWidget
, wurde die DScrollItem-Klasse hinzugefügt, die ein DWidget
erbt und ein Listenelement darstellt. Es enthält einen Link zu scrollable
, so dass beim Scrollen nur dyScroll
oder dxScroll
:
class SecondScreen extends BaseScreen { SecondScreen(FlutterDriver driver) : super(driver); DWidget get list => dWidget("list"); DScrollItem item(int index) => dScrollItem('item_$index', list); } ... final secondScreen = SecondScreen(driver); await runTestActions([ secondScreen.item(42).scrollUntilVisible(dyScroll: -300), ... ]);
Während der Tests können Sie Screenshots der Anwendung machen, und FlutterDriverHelper
verfügt über einen Screenshoter
, der Screenshots mit der aktuellen Uhrzeit im richtigen Ordner speichert und mit TestAction
.
Andere Probleme und ihre Lösungen
- Ich konnte keine Standardmethode zum Klicken auf Schaltflächen in den Zeit- / Datumsdialogen finden - ich muss
TestHooks
. TestHooks
können auch nützlich sein, um die aktuelle Uhrzeit / das aktuelle Datum während des Tests zu ändern. - In der Dropdown-Liste für
DropdownButtonFormField
Sie den key
nicht für DropdownMenuItem
, sondern für das DropdownMenuItem
dieses DropdownMenuItem
, da der Flutter Driver
ihn sonst nicht finden kann. Außerdem funktioniert das Scrollen in der Dropdown-Liste noch nicht ( Problem bei github.com ). - Die
FlutterDriver.getCenter
Methode gibt Future<DriverOffset>
, aber DriverOffset
nicht Teil der öffentlichen API ( DriverOffset
auf github.com ) - Es gibt einige problematischere und nicht offensichtliche Dinge, die bereits existieren. Sie können darüber in einem wunderbaren Artikel lesen. Besonders nützlich war die Möglichkeit, Tests auf dem Desktop auszuführen und den Status der Anwendung vor dem Start jedes Tests zurückzusetzen.
- Sie können Tests mit Github-Aktionen ausführen. Weitere Details hier .
Todo
Zukünftige FlutterDriverHelper
für FlutterDriverHelper
beinhalten:
- Automatisches Scrollen zum gewünschten Listenelement, wenn es zum Zeitpunkt des Zugriffs nicht auf dem Bildschirm angezeigt wird (wie in der Kaspresso- Bibliothek für Android). Wenn möglich, dann auch in beide Richtungen.
- Interceptors für Aktionen, die mit einem
Dwidget
oder DscrollItem
.
Kommentare und konstruktives Feedback sind willkommen.
Update (15.01.2020) : In Version 1.1.0 wurde TestAction
zu einer Klasse mit dem Feld String name
. Und dank dessen wurde die Protokollierung aller in der runTestActions
Methode ausgeführten Aktionen runTestActions
.