Saya pikir banyak orang sudah terbiasa dengan Flutter dan, setidaknya untuk kepentingan, mereka memulai aplikasi sederhana di atasnya. Sudah waktunya untuk memastikan bahwa semuanya berfungsi sesuai kebutuhan, dan tes integrasi akan membantu kami dalam hal ini.

Tes integrasi pada Flutter ditulis menggunakan Flutter Driver, di mana ada tutorial yang sederhana dan mudah dipahami di situs web resmi . Dalam strukturnya, tes semacam itu mirip dengan Espresso dari dunia Android. Pertama, Anda perlu menemukan elemen UI di layar:
final SerializableFinder button = find.byValueKey("button");
kemudian lakukan beberapa tindakan dengan mereka:
driver = await FlutterDriver.connect(); ... await driver.tap(button);
dan verifikasi bahwa elemen UI yang diperlukan dalam keadaan yang diinginkan:
final SerializableFinder text = find.byValueKey("text"); expect(await driver.getText(text), "some text");
Dengan contoh sederhana, tentu saja, semuanya terlihat elementer. Tetapi dengan pertumbuhan aplikasi yang sedang diuji dan peningkatan jumlah tes, saya tidak ingin menduplikasi pencarian elemen UI sebelum setiap tes. Selain itu, Anda perlu menyusun elemen-elemen UI ini, karena mungkin ada banyak layar. Untuk melakukan ini, buat tes menulis lebih nyaman.
Objek layar
Di Android ( Kakao ), masalah ini diselesaikan dengan mengelompokkan elemen UI dari setiap layar ke dalam Layar (Halaman-Objek). Pendekatan serupa dapat diterapkan di sini, dengan pengecualian di Flutter, untuk melakukan tindakan dengan elemen UI, Anda tidak hanya perlu Finder
(untuk mencari elemen UI), tetapi juga FlutterDriver
(untuk melakukan suatu tindakan), jadi Anda perlu menyimpan tautan ke FlutterDriver
di Screen
.
Untuk mendefinisikan setiap elemen UI, kami menambahkan kelas DWidget
(D - dari kata Dart dalam kasus ini). Untuk membuat DWidget
memerlukan FlutterDriver
, yang tindakannya akan dilakukan pada elemen UI ini, serta ValueKey
, yang bertepatan dengan ValueKey
Flutter dari widget dari aplikasi yang ingin kami berinteraksi:
class DWidget { final FlutterDriver _driver; final SerializableFinder _finder; DWidget(this._driver, dynamic valueKey) : _finder = find.byValueKey(valueKey); ...
find.byValueKey(…)
ketika secara manual membuat setiap DWidget
nyaman, oleh karena itu lebih baik meneruskan nilai ValueKey
ke ValueKey
, dan DWidget
itu sendiri akan mendapatkan SerializableFinder
diinginkan. Juga tidak terlalu nyaman untuk melewatkan FlutterDriver
secara manual saat membuat setiap DWidget
, sehingga Anda dapat menyimpan FlutterDriver
di BaseScreen
dan mentransfernya ke DWidget
, dan menambahkan metode baru untuk BaseScreen
untuk membuat BaseScreen
:
abstract class BaseScreen { final FlutterDriver _driver; BaseScreen(this._driver); DWidget dWidget(dynamic key) => DWidget(_driver, key); ...
Dengan demikian, membuat kelas Layar dan memasukkan elemen UI di dalamnya akan lebih mudah:
class MainScreen extends BaseScreen { MainScreen(FlutterDriver driver) : super(driver); DWidget get button => dWidget('button'); DWidget get textField => dWidget('text_field'); ... }
Menyingkirkan await
Hal lain yang tidak terlalu nyaman ketika menulis tes dengan FlutterDriver
adalah kebutuhan untuk menambahkan await
sebelum setiap tindakan:
await driver.tap(button); await driver.scrollUntilVisible(list, checkBox); await driver.tap(checkBox); await driver.tap(text); await driver.enterText("some text");
Melupakan await
itu mudah, dan tanpa itu, tes tidak akan berfungsi dengan benar, karena metode driver
mengembalikan Future<void>
dan ketika mereka dipanggil tanpa await
dieksekusi sampai yang pertama await
di dalam metode, dan sisa dari metode ini "ditunda sampai nanti".
Anda dapat TestAction
dengan membuat TestAction
yang akan “membungkus” Future
sehingga kami dapat menunggu sampai satu tindakan selesai sebelum melanjutkan ke yang berikutnya:
typedef TestAction = Future<void> Function();
(pada dasarnya, TestAction
adalah fungsi apa pun (atau lambda) yang mengembalikan Future<void>
)
Sekarang Anda dapat dengan mudah menjalankan urutan TestAction
tanpa harus menunggu:
Future<void> runTestActions(Iterable<TestAction> actions) async { for (final action in actions) { await action(); } }
DWidget
digunakan untuk berinteraksi dengan elemen UI, dan akan sangat nyaman jika tindakan ini adalah TestAction
sehingga mereka dapat digunakan dalam metode runTestAction
. Untuk melakukan ini, kelas DWidget
akan memiliki metode tindakan:
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); }; ... }
Sekarang Anda dapat menulis tes sebagai berikut:
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"), ]);
Jika Anda perlu melakukan beberapa tindakan di runTestActions
yang tidak terkait dengan DWidget
, maka Anda hanya perlu membuat lambda yang mengembalikan Future<void>
:
await runTestActions([ mainScreen.result.hasText("summa = 0"), () => driver.requestData("some_message"), () async => print("some_text"), mainScreen.field_1.setText("3"), ]);
FlutterDriverHelper
FlutterDriver
memiliki beberapa metode untuk berinteraksi dengan elemen UI (menekan, menerima dan memasukkan teks, menggulir, dll.) Dan untuk metode ini DWidget
memiliki metode terkait yang mengembalikan TestAction
.
Untuk kenyamanan, semua kode yang dijelaskan dalam artikel ini diterbitkan sebagai pustaka FlutterDriverHelper di pub.dev .
Untuk menggulir daftar di mana elemen dibuat secara dinamis (misalnya, ListView.builder
), FlutterDriver
memiliki metode scrollUntilVisible
:
Future<void> scrollUntilVisible( SerializableFinder scrollable, SerializableFinder item, { double alignment = 0.0, double dxScroll = 0.0, double dyScroll = 0.0, Duration timeout, }) async { ... }
Metode ini menggulir widget yang dapat digulir ke arah yang ditentukan hingga widget item
muncul di layar (atau hingga waktu timeout
). Agar tidak meneruskan scrollable
pada setiap scroll, kelas DScrollItem ditambahkan, yang mewarisi DWidget
dan mewakili item daftar. Ini berisi tautan ke scrollable
, jadi ketika menggulirnya hanya tinggal menentukan dyScroll
atau 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), ... ]);
Selama pengujian, Anda dapat mengambil tangkapan layar aplikasi, dan FlutterDriverHelper
memiliki Screenshoter
yang menyimpan tangkapan layar ke folder kanan dengan waktu saat ini dan dapat bekerja dengan TestAction
.
Masalah lain dan solusinya
- Saya tidak dapat menemukan cara standar untuk mengklik tombol pada dialog waktu / tanggal - Saya harus menggunakan
TestHooks
. TestHooks
juga dapat berguna untuk mengubah waktu / tanggal saat ini selama tes. - Di daftar dropdown untuk
DropdownButtonFormField
Anda perlu menentukan key
bukan untuk DropdownMenuItem
, tetapi untuk child
DropdownMenuItem
ini, jika tidak Flutter Driver
tidak akan dapat menemukannya. Selain itu, menggulir dalam daftar drop-down belum berfungsi ( Masalah di github.com ). - metode
FlutterDriver.getCenter
mengembalikan Future<DriverOffset>
, tetapi DriverOffset
bukan bagian dari API publik ( Masalah pada github.com ) - Ada beberapa hal yang lebih bermasalah dan tidak jelas yang sudah ada. Anda dapat membacanya di artikel yang luar biasa . Yang sangat berguna adalah kemampuan untuk menjalankan tes pada desktop dan mengatur ulang keadaan aplikasi sebelum dimulainya setiap tes.
- Anda dapat menjalankan tes menggunakan Tindakan Github. Lebih detail di sini .
Todo
FlutterDriverHelper
Masa Depan untuk FlutterDriverHelper
meliputi:
- gulir otomatis ke item daftar yang diinginkan jika pada saat mengaksesnya tidak terlihat di layar (seperti yang dilakukan di perpustakaan Kaspresso untuk Android). Jika memungkinkan, maka bahkan di kedua arah.
- pencegat untuk tindakan yang dilakukan dengan
Dwidget
atau DscrollItem
.
Komentar dan umpan balik konstruktif dipersilakan.
Pembaruan (15/01/2020) : dalam versi 1.1.0 TestAction
menjadi kelas, dengan bidang String name
. Dan terima kasih untuk ini, pencatatan semua tindakan yang dilakukan dalam metode runTestActions
.