Tes Integrasi Flutter - Mudah

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

Menggunakan TestAction dalam DWidget


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 .

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


All Articles