Je pense que beaucoup de gens connaissent déjà Flutter et, au moins par intérêt, ils ont lancé des applications simples dessus. Il est temps de s'assurer que tout fonctionne comme il le faut, et les tests d'intégration nous y aideront.

Les tests d'intégration sur Flutter sont écrits à l'aide du pilote Flutter, pour lequel il existe un tutoriel simple et intuitif sur le site officiel . Dans leur structure, ces tests sont similaires à Espresso du monde Android. Vous devez d'abord trouver les éléments de l'interface utilisateur à l'écran:
final SerializableFinder button = find.byValueKey("button");
puis effectuez quelques actions avec eux:
driver = await FlutterDriver.connect(); ... await driver.tap(button);
et vérifiez que les éléments d'interface utilisateur requis sont dans l'état souhaité:
final SerializableFinder text = find.byValueKey("text"); expect(await driver.getText(text), "some text");
Avec un exemple simple, bien sûr, tout semble élémentaire. Mais avec la croissance de l'application testée et l'augmentation du nombre de tests, je ne veux pas dupliquer la recherche d'éléments d'interface utilisateur avant chaque test. De plus, vous devrez structurer ces éléments d'interface utilisateur, car il peut y avoir de nombreux écrans. Pour ce faire, rendez l'écriture des tests plus pratique.
Objets d'écran
Dans Android ( Kakao ), ce problème est résolu en regroupant les éléments de l'interface utilisateur de chaque écran dans Screen (Page-Object). Une approche similaire peut être appliquée ici, à l'exception que dans Flutter, pour effectuer des actions avec des éléments d'interface utilisateur, vous avez besoin non seulement du Finder
(pour rechercher un élément d'interface utilisateur), mais aussi de FlutterDriver
(pour effectuer une action), vous devez donc stocker un lien vers FlutterDriver
dans Screen
.
Pour définir chaque élément de l'interface utilisateur, nous ajoutons la classe DWidget
(D - du mot Dart dans ce cas). Pour créer un DWidget
aurez besoin d'un FlutterDriver
, avec lequel les actions seront effectuées sur cet élément d'interface utilisateur, ainsi que d'un ValueKey
, qui coïncide avec le ValueKey
Flutter du widget de l'application avec laquelle nous voulons interagir:
class DWidget { final FlutterDriver _driver; final SerializableFinder _finder; DWidget(this._driver, dynamic valueKey) : _finder = find.byValueKey(valueKey); ...
find.byValueKey(…)
lors de la création manuelle de chaque DWidget
, il est donc préférable de transmettre la valeur ValueKey
au ValueKey
, et le DWidget
lui-même obtiendra le SerializableFinder
souhaité. Il n'est pas non plus très pratique de transmettre manuellement FlutterDriver
lors de la création de chaque DWidget
, vous pouvez donc stocker FlutterDriver
dans BaseScreen
et le transférer vers DWidget
, et ajouter une nouvelle méthode pour BaseScreen
pour créer BaseScreen
:
abstract class BaseScreen { final FlutterDriver _driver; BaseScreen(this._driver); DWidget dWidget(dynamic key) => DWidget(_driver, key); ...
Ainsi, il sera beaucoup plus facile de créer des classes Screens et d'y intégrer des éléments d'interface utilisateur:
class MainScreen extends BaseScreen { MainScreen(FlutterDriver driver) : super(driver); DWidget get button => dWidget('button'); DWidget get textField => dWidget('text_field'); ... }
Se débarrasser d' await
Une autre chose pas très pratique lors de l'écriture de tests avec FlutterDriver
est la nécessité d'ajouter await
avant chaque action:
await driver.tap(button); await driver.scrollUntilVisible(list, checkBox); await driver.tap(checkBox); await driver.tap(text); await driver.enterText("some text");
Oublier l' await
est facile, et sans cela, les tests ne fonctionneront pas correctement, car les méthodes du driver
renvoient Future<void>
et lorsqu'elles sont appelées sans await
sont exécutées jusqu'à la première await
à l'intérieur de la méthode, et le reste de la méthode est «reporté à plus tard».
Vous pouvez TestAction
ce TestAction
en créant une TestAction
qui « TestAction
» Future
afin que nous puissions attendre qu'une action soit terminée avant de passer à la suivante:
typedef TestAction = Future<void> Function();
(essentiellement, TestAction
est une fonction (ou lambda) qui renvoie un Future<void>
)
Maintenant, vous pouvez facilement exécuter la séquence TestAction
sans attente inutile:
Future<void> runTestActions(Iterable<TestAction> actions) async { for (final action in actions) { await action(); } }
DWidget
utilisé pour interagir avec les éléments de l'interface utilisateur, et il sera très pratique si ces actions sont TestAction
afin qu'elles puissent être utilisées dans la méthode runTestAction
. Pour ce faire, la classe DWidget
aura des méthodes d'action:
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); }; ... }
Vous pouvez maintenant écrire des tests comme suit:
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"), ]);
Si vous devez effectuer une action dans runTestActions
qui n'est pas liée à DWidget
, il vous suffit de créer un lambda qui renvoie un Future<void>
:
await runTestActions([ mainScreen.result.hasText("summa = 0"), () => driver.requestData("some_message"), () async => print("some_text"), mainScreen.field_1.setText("3"), ]);
FlutterDriverHelper
FlutterDriver
dispose de plusieurs méthodes pour interagir avec les éléments de l'interface utilisateur (pression, réception et saisie de texte, défilement, etc.) et pour ces méthodes, DWidget
a des méthodes correspondantes qui renvoient TestAction
.
Pour plus de commodité, tout le code décrit dans cet article est publié en tant FlutterDriverHelper
bibliothèque FlutterDriverHelper sur pub.dev .
Pour faire défiler les listes dans lesquelles les éléments sont créés dynamiquement (par exemple, ListView.builder
), FlutterDriver
a une méthode scrollUntilVisible
:
Future<void> scrollUntilVisible( SerializableFinder scrollable, SerializableFinder item, { double alignment = 0.0, double dxScroll = 0.0, double dyScroll = 0.0, Duration timeout, }) async { ... }
Cette méthode fait défiler le widget déroulant dans la direction spécifiée jusqu'à ce que le widget d' item
apparaisse à l'écran (ou jusqu'à ce qu'une timeout
). Afin de ne pas passer scrollable
sur chaque scroll, la classe DScrollItem a été ajoutée, qui hérite d'un DWidget
et représente un élément de liste. Il contient un lien vers scrollable
, donc lors du défilement il ne reste plus qu'à spécifier dyScroll
ou 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), ... ]);
Pendant les tests, vous pouvez prendre des captures d'écran de l'application et FlutterDriverHelper
possède un Screenshoter
qui enregistre les captures d'écran dans le bon dossier avec l'heure actuelle et peut fonctionner avec TestAction
.
Autres problèmes et leurs solutions
- Je ne pouvais pas trouver un moyen standard de cliquer sur les boutons dans les boîtes de dialogue heure / date - je dois utiliser
TestHooks
. TestHooks
peuvent également être utiles pour changer l'heure / la date actuelle pendant le test. - Dans la liste déroulante de
DropdownButtonFormField
vous devez spécifier la key
non pas pour DropdownMenuItem
, mais pour l' child
ce DropdownMenuItem
, sinon le Flutter Driver
ne pourra pas le trouver. De plus, le défilement dans la liste déroulante ne fonctionne pas encore ( problème sur github.com ). - la méthode
FlutterDriver.getCenter
renvoie Future<DriverOffset>
, mais DriverOffset
ne DriverOffset
pas partie de l'API publique ( problème sur github.com ) - Il y a quelques choses plus problématiques et pas évidentes qui existent déjà. Vous pouvez les lire dans un merveilleux article . La possibilité d'exécuter des tests sur le bureau et de réinitialiser l'état de l'application avant le début de chaque test était particulièrement utile.
- Vous pouvez exécuter des tests à l'aide des actions Github. Plus de détails ici .
Todo
Les futurs FlutterDriverHelper
pour FlutterDriverHelper
comprennent:
- faites défiler automatiquement jusqu'à l'élément de liste souhaité, si au moment d'y accéder, il n'est pas visible à l'écran (comme cela se fait dans la bibliothèque Kaspresso pour Android). Si possible, alors même dans les deux sens.
- intercepteurs pour les actions effectuées avec un
Dwidget
ou DscrollItem
.
Les commentaires et les commentaires constructifs sont les bienvenus.
Mise à jour (15/01/2020) : dans la version 1.1.0, TestAction
est devenue une classe, avec le champ String name
. Et grâce à cela, la journalisation de toutes les actions effectuées dans la méthode runTestActions
été runTestActions
.