Flatterintegrationstests - ganz einfach

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(); } } 

Verwenden von TestAction in einem DWidget


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 .

Source: https://habr.com/ru/post/de483468/


All Articles