Cara menguji aplikasi Flutter

1. Pengantar

Flutter adalah toolkit UI Google untuk membangun aplikasi yang menarik dan dikompilasi secara native dari satu codebase untuk perangkat seluler, web, dan desktop.

Di codelab ini, Anda akan mem-build dan menguji aplikasi Flutter sederhana. Aplikasi akan menggunakan paket Provider untuk mengelola status.

Yang akan Anda pelajari

  • Cara membuat pengujian widget menggunakan framework pengujian widget
  • Cara membuat pengujian integrasi untuk menguji performa dan UI aplikasi menggunakan library integration_test
  • Cara menguji class data (penyedia) dengan bantuan pengujian unit

Yang akan Anda bangun

Di codelab ini, Anda akan mulai mem-build aplikasi sederhana dengan daftar item. Kami menyediakan kode sumber bagi Anda sehingga Anda dapat langsung ke pengujian. Aplikasi ini mendukung operasi berikut:

  • Menambahkan item ke favorit
  • Melihat daftar favorit
  • Menghapus item dari daftar favorit

Setelah aplikasi selesai, Anda akan menulis pengujian berikut:

  • Pengujian unit untuk memvalidasi operasi tambahkan dan hapus
  • Pengujian widget untuk halaman beranda dan favorit
  • Pengujian dan performa untuk seluruh aplikasi yang menggunakan pengujian integrasi

GIF aplikasi yang berjalan di Android

Apa yang ingin Anda pelajari dari codelab ini?

Saya baru mengenal topik ini, jadi saya ingin mendapatkan ringkasan yang jelas. Saya sedikit paham soal topik ini, tetapi saya perlu mengingat kembali. Saya sedang mencari kode contoh untuk digunakan dalam project saya. Saya sedang mencari penjelasan tentang sesuatu yang spesifik.

2. Menyiapkan lingkungan pengembangan Flutter Anda

Anda memerlukan dua software untuk menyelesaikan lab ini—Flutter SDK dan editor.

Anda dapat menjalankan codelab menggunakan salah satu perangkat berikut:

  • Perangkat Android atau iOS fisik yang terhubung ke komputer dan disetel ke Mode developer.
  • Simulator iOS (perlu menginstal alat Xcode).
  • Android Emulator (perlu penyiapan di Android Studio).
  • Browser (perlu Chrome untuk proses debug).
  • Aplikasi desktop Windows, Linux, atau macOS. Anda harus mengembangkan aplikasi di platform tempat Anda berencana untuk men-deploy-nya. Jadi, jika ingin mengembangkan aplikasi desktop Windows, Anda harus mengembangkannya di Windows untuk mengakses rantai build yang sesuai. Ada persyaratan spesifik per sistem operasi yang dibahas secara mendetail di docs.flutter.dev/desktop.

3. Memulai

Membuat dependensi update dan aplikasi Flutter baru

Codelab ini berfokus untuk menguji aplikasi seluler Flutter. Anda dapat dengan cepat membuat aplikasi yang akan diuji menggunakan file sumber yang Anda salin dan tempel. Kemudian, bagian codelab lainnya berfokus untuk mempelajari berbagai jenis pengujian.

a3c16fc17be25f6c.pngBuat aplikasi Flutter dengan template sederhana menggunakan petunjuk di Memulai aplikasi Flutter pertama Anda, atau di command line sebagai berikut.

$ flutter create testing_app

a3c16fc17be25f6c.pngTambahkan dependensi pub di command line. Untuk memudahkan pengelolaan status, tambahkan provider:

$ cd testing_app
$ flutter pub add provider
Resolving dependencies...
  collection 1.17.0 (1.17.1 available)
  js 0.6.5 (0.6.7 available)
  matcher 0.12.13 (0.12.14 available)
  meta 1.8.0 (1.9.0 available)
+ nested 1.0.0
  path 1.8.2 (1.8.3 available)
+ provider 6.0.5
  test_api 0.4.16 (0.4.18 available)
Changed 2 dependencies!

Untuk pengujian kode Flutter secara otomatis di perangkat dan emulator, tambahkan integration_test:

$ flutter pub add --dev --sdk=flutter integration_test
Resolving dependencies...
+ archive 3.3.2 (3.3.6 available)
  collection 1.17.0 (1.17.1 available)
+ crypto 3.0.2
+ file 6.1.4
+ flutter_driver 0.0.0 from sdk flutter
+ fuchsia_remote_debug_protocol 0.0.0 from sdk flutter
+ integration_test 0.0.0 from sdk flutter
  js 0.6.5 (0.6.7 available)
  matcher 0.12.13 (0.12.14 available)
  meta 1.8.0 (1.9.0 available)
  path 1.8.2 (1.8.3 available)
+ platform 3.1.0
+ process 4.2.4
+ sync_http 0.3.1
  test_api 0.4.16 (0.4.18 available)
+ typed_data 1.3.1
+ vm_service 9.4.0 (11.0.1 available)
+ webdriver 3.0.1 (3.0.2 available)
Changed 12 dependencies!

Untuk API tingkat lanjut guna menguji aplikasi Flutter yang berjalan di perangkat dan emulator sungguhan, tambahkan flutter_driver:

$ flutter pub add --dev --sdk=flutter flutter_driver
Resolving dependencies...
  archive 3.3.2 (3.3.6 available)
  collection 1.17.0 (1.17.1 available)
  js 0.6.5 (0.6.7 available)
  matcher 0.12.13 (0.12.14 available)
  meta 1.8.0 (1.9.0 available)
  path 1.8.2 (1.8.3 available)
  test_api 0.4.16 (0.4.18 available)
  vm_service 9.4.0 (11.0.1 available)
  webdriver 3.0.1 (3.0.2 available)
Got dependencies!

Untuk alat pengujian umum, tambahkan test:

$ flutter pub add --dev test
Resolving dependencies...
+ _fe_analyzer_shared 52.0.0
+ analyzer 5.4.0
  archive 3.3.2 (3.3.6 available)
+ args 2.3.2
  collection 1.17.0 (1.17.1 available)
+ convert 3.1.1
+ coverage 1.6.3
+ frontend_server_client 3.2.0
+ glob 2.1.1
+ http_multi_server 3.2.1
+ http_parser 4.0.2
+ io 1.0.4
  js 0.6.5 (0.6.7 available)
+ logging 1.1.1
  matcher 0.12.13 (0.12.14 available)
  meta 1.8.0 (1.9.0 available)
+ mime 1.0.4
+ node_preamble 2.0.1
+ package_config 2.1.0
  path 1.8.2 (1.8.3 available)
+ pool 1.5.1
+ pub_semver 2.1.3
+ shelf 1.4.0
+ shelf_packages_handler 3.0.1
+ shelf_static 1.1.1
+ shelf_web_socket 1.0.3
+ source_map_stack_trace 2.1.1
+ source_maps 0.10.11
+ test 1.22.0 (1.23.0 available)
  test_api 0.4.16 (0.4.18 available)
+ test_core 0.4.20 (0.4.23 available)
  vm_service 9.4.0 (11.0.1 available)
+ watcher 1.0.2
+ web_socket_channel 2.3.0
  webdriver 3.0.1 (3.0.2 available)
+ webkit_inspection_protocol 1.2.0
+ yaml 3.1.1
Changed 28 dependencies!

Untuk menangani navigasi aplikasi, tambahkan go_router:

$ flutter pub add go_router
Resolving dependencies...
  archive 3.3.2 (3.3.6 available)
  collection 1.17.0 (1.17.1 available)
+ flutter_web_plugins 0.0.0 from sdk flutter
+ go_router 6.0.4
  js 0.6.5 (0.6.7 available)
  matcher 0.12.13 (0.12.14 available)
  meta 1.8.0 (1.9.0 available)
  path 1.8.2 (1.8.3 available)
  test 1.22.0 (1.23.0 available)
  test_api 0.4.16 (0.4.18 available)
  test_core 0.4.20 (0.4.23 available)
  vm_service 9.4.0 (11.0.1 available)
  webdriver 3.0.1 (3.0.2 available)
Changed 2 dependencies!

Dependensi berikut seharusnya sudah ditambahkan ke pubspec.yaml Anda:

Di bagian dependencies:

dependencies:
  provider: ^6.0.5
  go_router: ^6.0.4

Di bagian dev_dependencies:

dev_dependencies:
  integration_test:
    sdk: flutter
  flutter_driver:
    sdk: flutter
  test: ^1.22.0

a3c16fc17be25f6c.pngBuka project di editor kode pilihan Anda, lalu jalankan aplikasi. Atau, jalankan aplikasi di command line sebagai berikut.

$ flutter run

4. Membangun aplikasi

Berikutnya, Anda akan membangun aplikasi sehingga Anda dapat mengujinya. Aplikasi berisi file berikut:

  • lib/models/favorites.dart - membuat class model untuk daftar favorit
  • lib/screens/favorites.dart - membuat tata letak untuk daftar favorit
  • lib/screens/home.dart - membuat daftar item
  • lib/main.dart - file utama tempat aplikasi dimulai

Pertama, buat model Favorites di lib/models/favorites.dart

a3c16fc17be25f6c.pngBuat direktori baru bernama models di direktori lib, lalu buat file baru bernama favorites.dart. Di file tersebut, tambahkan kode berikut:

lib/models/favorites.dart

import 'package:flutter/material.dart';

/// The [Favorites] class holds a list of favorite items saved by the user.
class Favorites extends ChangeNotifier {
  final List<int> _favoriteItems = [];

  List<int> get items => _favoriteItems;

  void add(int itemNo) {
    _favoriteItems.add(itemNo);
    notifyListeners();
  }

  void remove(int itemNo) {
    _favoriteItems.remove(itemNo);
    notifyListeners();
  }
}

Tambahkan Halaman favorit di lib/screens/favorites.dart

a3c16fc17be25f6c.pngBuat direktori baru bernama screens di direktori lib, lalu di direktori tersebut, buat file baru bernama favorites.dart. Di file tersebut, tambahkan kode berikut:

lib/screens/favorites.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../models/favorites.dart';

class FavoritesPage extends StatelessWidget {
  const FavoritesPage({super.key});

  static String routeName = 'favorites_page';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Favorites'),
      ),
      body: Consumer<Favorites>(
        builder: (context, value, child) => ListView.builder(
          itemCount: value.items.length,
          padding: const EdgeInsets.symmetric(vertical: 16),
          itemBuilder: (context, index) => FavoriteItemTile(value.items[index]),
        ),
      ),
    );
  }
}

class FavoriteItemTile extends StatelessWidget {
  const FavoriteItemTile(this.itemNo, {super.key});

  final int itemNo;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
        ),
        title: Text(
          'Item $itemNo',
          key: Key('favorites_text_$itemNo'),
        ),
        trailing: IconButton(
          key: Key('remove_icon_$itemNo'),
          icon: const Icon(Icons.close),
          onPressed: () {
            Provider.of<Favorites>(context, listen: false).remove(itemNo);
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('Removed from favorites.'),
                duration: Duration(seconds: 1),
              ),
            );
          },
        ),
      ),
    );
  }
}

Tambahkan Halaman beranda di lib/screens/home.dart

a3c16fc17be25f6c.pngDi direktori lib/screens, buat file baru lainnya yang bernama home.dart. Di lib/screens/home.dart, tambahkan kode berikut:

lib/screens/home.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../models/favorites.dart';
import 'favorites.dart';

class HomePage extends StatelessWidget {
  static String routeName = '/';

  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Testing Sample'),
        actions: <Widget>[
          TextButton.icon(
            onPressed: () {
              context.go('/${FavoritesPage.routeName}');
            },
            icon: const Icon(Icons.favorite_border),
            label: const Text('Favorites'),
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: 100,
        cacheExtent: 20.0,
        padding: const EdgeInsets.symmetric(vertical: 16),
        itemBuilder: (context, index) => ItemTile(index),
      ),
    );
  }
}

class ItemTile extends StatelessWidget {
  final int itemNo;

  const ItemTile(this.itemNo, {super.key});

  @override
  Widget build(BuildContext context) {
    var favoritesList = Provider.of<Favorites>(context);

    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
        ),
        title: Text(
          'Item $itemNo',
          key: Key('text_$itemNo'),
        ),
        trailing: IconButton(
          key: Key('icon_$itemNo'),
          icon: favoritesList.items.contains(itemNo)
              ? const Icon(Icons.favorite)
              : const Icon(Icons.favorite_border),
          onPressed: () {
            !favoritesList.items.contains(itemNo)
                ? favoritesList.add(itemNo)
                : favoritesList.remove(itemNo);
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text(favoritesList.items.contains(itemNo)
                    ? 'Added to favorites.'
                    : 'Removed from favorites.'),
                duration: const Duration(seconds: 1),
              ),
            );
          },
        ),
      ),
    );
  }
}

Ganti konten lib/main.dart

a3c16fc17be25f6c.pngGanti konten lib/main.dart dengan kode berikut:

lib/main.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'models/favorites.dart';
import 'screens/favorites.dart';
import 'screens/home.dart';

void main() {
  runApp(const TestingApp());
}

final _router = GoRouter(
  routes: [
    GoRoute(
      path: HomePage.routeName,
      builder: (context, state) {
        return const HomePage();
      },
      routes: [
        GoRoute(
          path: FavoritesPage.routeName,
          builder: (context, state) {
            return const FavoritesPage();
          },
        ),
      ],
    ),
  ],
);

class TestingApp extends StatelessWidget {
  const TestingApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Favorites>(
      create: (context) => Favorites(),
      child: MaterialApp.router(
        title: 'Testing Sample',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          useMaterial3: true,
        ),
        routerConfig: _router,
      ),
    );
  }
}

Aplikasi sudah selesai, tetapi belum diuji.

a3c16fc17be25f6c.pngJalankan aplikasi. Tampilannya akan terlihat seperti screenshot ini:

b74f843e42a28b0f.png

Aplikasi menampilkan daftar item. Ketuk ikon berbentuk hati di baris mana saja untuk mengisi hati dan menambahkan item ke daftar favorit. Tombol Favorit di AppBar mengarahkan Anda ke layar kedua yang berisi daftar favorit.

Kini aplikasi sudah siap untuk pengujian. Anda akan mulai menguji aplikasi di langkah berikutnya.

5. Melakukan pengujian unit pada penyedia

Anda akan memulai dengan melakukan pengujian unit pada model favorites. Apa itu pengujian unit? Pengujian unit memverifikasi bahwa setiap unit software, baik berupa fungsi, objek, ataupun widget, menjalankan tugasnya dengan benar.

Semua file pengujian di aplikasi Flutter, kecuali untuk pengujian integrasi, ditempatkan di direktori test.

Hapus test/widget_test.dart

a3c16fc17be25f6c.pngSebelum memulai pengujian, hapus file widget_test.dart. Anda akan menambahkan file pengujian Anda sendiri.

Membuat file pengujian baru

Pertama, Anda akan menguji metode add() di model Favorites untuk memastikan bahwa item baru ditambahkan ke daftar, dan daftar tersebut mencerminkan perubahan. Berdasarkan konvensi, struktur direktori di direktori test meniru yang ada di direktori lib dan file Dart memiliki nama yang sama, dengan tambahan _test.

a3c16fc17be25f6c.pngBuat direktori models di direktori test. Di direktori baru ini, buat file favorites_test.dart dengan konten berikut:

test/models/favorites_test.dart

import 'package:test/test.dart';
import 'package:testing_app/models/favorites.dart';

void main() {
  group('Testing App Provider', () {
    var favorites = Favorites();

    test('A new item should be added', () {
      var number = 35;
      favorites.add(number);
      expect(favorites.items.contains(number), true);
    });
  });
}

Framework pengujian Flutter memungkinkan Anda mengikat pengujian serupa yang terkait satu sama lain dalam grup. Mungkin terdapat beberapa grup dalam satu file pengujian yang ditujukan untuk menguji bagian yang berbeda dari file yang sesuai di direktori lib.

Metode test() menggunakan dua parameter posisi: description pengujian dan callback tempat sebenarnya Anda menulis pengujian tersebut.

a3c16fc17be25f6c.pngCoba hapus item dari daftar. Masukkan pengujian berikut dalam grup Testing App Provider yang sama:

test/models/favorites_test.dart

test('An item should be removed', () {
  var number = 45;
  favorites.add(number);
  expect(favorites.items.contains(number), true);
  favorites.remove(number);
  expect(favorites.items.contains(number), false);
});

Menjalankan pengujian

a3c16fc17be25f6c.pngDi command line, buka direktori utama project dan masukkan perintah berikut:

$ flutter test test/models/favorites_test.dart

Jika semuanya berfungsi, Anda akan melihat pesan seperti berikut:

00:06 +2: All tests passed!

File pengujian yang lengkap: test/models/favorites_test.dart.

Untuk informasi selengkapnya terkait pengujian unit, kunjungi An introduction to unit testing.

6. Pengujian widget

Pada langkah ini, Anda akan menambahkan kode untuk menguji widget. Pengujian widget bersifat unik untuk Flutter, tempat Anda dapat menguji setiap widget secara terpisah. Langkah ini menguji layar HomePage dan FavoritesPage satu per satu.

Pengujian widget menggunakan fungsi testWidget(), bukan fungsi test(). Seperti fungsi test(), fungsi testWidget() menggunakan dua parameter: description, dan callback, tetapi callback-nya menggunakan WidgetTester sebagai argumennya.

Pengujian widget menggunakan TestFlutterWidgetsBinding, yaitu class yang menyediakan resource yang sama ke widget Anda seperti halnya di aplikasi yang berjalan, misalnya informasi tentang ukuran layar, kemampuan untuk menjadwalkan animasi, tetapi tanpa berjalan di dalam aplikasi. Sebagai gantinya, lingkungan virtual digunakan untuk membuat instance widget, lalu menjalankan pengujian hasil. Di sini, pumpWidget memulai proses dengan memberi tahu framework untuk memasang dan mengukur widget tertentu seperti yang dilakukannya di aplikasi.

Framework pengujian widget memberikan pencari untuk menemukan widget, misalnya text(), byType(), dan byIcon().. Framework ini juga memungkinkan pencocok memverifikasi hasil.

Mulai dengan menguji widget HomePage.

Membuat file pengujian baru

Pengujian pertama memastikan apakah men-scroll HomePage berfungsi dengan baik.

a3c16fc17be25f6c.pngBuat file baru di direktori test lalu beri nama home_test.dart. Di file yang baru dibuat, tambahkan kode berikut:

test/home_test.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/home.dart';

Widget createHomeScreen() => ChangeNotifierProvider<Favorites>(
      create: (context) => Favorites(),
      child: const MaterialApp(
        home: HomePage(),
      ),
    );

void main() {
  group('Home Page Widget Tests', () {
    testWidgets('Testing Scrolling', (tester) async {
      await tester.pumpWidget(createHomeScreen());
      expect(find.text('Item 0'), findsOneWidget);
      await tester.fling(
        find.byType(ListView),
        const Offset(0, -200),
        3000,
      );
      await tester.pumpAndSettle();
      expect(find.text('Item 0'), findsNothing);
    });
  });
}

Fungsi createHomeScreen() digunakan untuk membuat aplikasi yang memuat widget yang akan diuji di MaterialApp, yang digabungkan menjadi ChangeNotifierProvider. Widget HomePage memerlukan kedua widget ini berada di atasnya di hierarki widget agar dapat mewarisi dari kedua widget tersebut dan mendapatkan akses ke data yang ditawarkan. Fungsi ini diteruskan sebagai parameter ke fungsi pumpWidget().

Berikutnya, uji apakah framework dapat menemukan ListView yang dirender ke layar.

a3c16fc17be25f6c.pngTambahkan cuplikan kode berikut ke home_test.dart:

test/home_test.dart

group('Home Page Widget Tests', () {

  // BEGINNING OF NEW CONTENT
  testWidgets('Testing if ListView shows up', (tester) async {
    await tester.pumpWidget(createHomeScreen());
    expect(find.byType(ListView), findsOneWidget);
  });
  // END OF NEW CONTENT

    testWidgets('Testing Scrolling', (tester) async {
      await tester.pumpWidget(createHomeScreen());
      expect(find.text('Item 0'), findsOneWidget);
      await tester.fling(
        find.byType(ListView),
        const Offset(0, -200),
        3000,
      );
      await tester.pumpAndSettle();
      expect(find.text('Item 0'), findsNothing);
    });
});

Menjalankan pengujian

Pertama-tama, jalankan pengujian dengan cara yang sama seperti saat Anda menjalankan pengujian unit, menggunakan perintah:

$ flutter test test/home_test.dart

Pengujian akan berlangsung cepat, dan Anda akan melihat pesan seperti ini:

00:02 +2: All tests passed!

Anda juga dapat menjalankan pengujian widget menggunakan perangkat atau emulator, yang memungkinkan Anda melihat pengujian yang berjalan. Hal ini juga memberi Anda kemampuan untuk menggunakan hot restart.

a3c16fc17be25f6c.pngHubungkan perangkat ke sumber listrik atau mulai emulator Anda. Anda juga dapat menjalankan pengujian sebagai aplikasi desktop.

a3c16fc17be25f6c.pngDari command line, buka direktori utama project lalu masukkan perintah berikut:

$ flutter run test/home_test.dart

Anda mungkin perlu memilih perangkat tempat pengujian akan dijalankan. Dalam hal ini, ikuti petunjuk lalu pilih perangkatnya:

Multiple devices found:
Linux (desktop) • linux  • linux-x64      • Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web)    • chrome • web-javascript • Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"):

Jika semuanya berfungsi, Anda akan melihat output mirip seperti berikut:

Launching test/home_test.dart on Linux in debug mode...
Building Linux application...
flutter: 00:00 +0: Home Page Widget Tests Testing if ListView shows up
Syncing files to device Linux...                                    62ms

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

💪 Running with sound null safety 💪

An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:00 +1: Home Page Widget Tests Testing Scrolling
The Flutter DevTools debugger and profiler on Linux is available at:
http://127.0.0.1:9100?uri=http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:02 +2: All tests passed!

Berikutnya, Anda akan mengubah file pengujian lalu menekan Shift + R untuk melakukan hot restart pada aplikasi dan menjalankan ulang semua pengujian. Jangan hentikan aplikasi.

a3c16fc17be25f6c.pngTambahkan pengujian lainnya ke grup yang menguji widget HomePage. Salin pengujian berikut ke file Anda:

test/home_test.dart

testWidgets('Testing IconButtons', (tester) async {
  await tester.pumpWidget(createHomeScreen());
  expect(find.byIcon(Icons.favorite), findsNothing);
  await tester.tap(find.byIcon(Icons.favorite_border).first);
  await tester.pumpAndSettle(const Duration(seconds: 1));
  expect(find.text('Added to favorites.'), findsOneWidget);
  expect(find.byIcon(Icons.favorite), findsWidgets);
  await tester.tap(find.byIcon(Icons.favorite).first);
  await tester.pumpAndSettle(const Duration(seconds: 1));
  expect(find.text('Removed from favorites.'), findsOneWidget);
  expect(find.byIcon(Icons.favorite), findsNothing);
});

Pengujian ini memastikan bahwa mengetuk IconButton akan mengubah Icons.favorite_border (hati yang kosong) menjadi Icons.favorite (hati yang terisi), kemudian kembali menjadi Icons.favorite_border ketika diketuk lagi.

a3c16fc17be25f6c.pngTekan Shift + R. Tindakan ini akan memulai hot restart pada aplikasi dan menjalan ulang semua pengujian.

File pengujian lengkap: test/home_test.dart.

a3c16fc17be25f6c.pngGunakan proses yang sama untuk menguji FavoritesPage dengan kode berikut. Ikuti langkah-langkah yang sama dan jalankan.

test/favorites_test.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/favorites.dart';

late Favorites favoritesList;

Widget createFavoritesScreen() => ChangeNotifierProvider<Favorites>(
      create: (context) {
        favoritesList = Favorites();
        return favoritesList;
      },
      child: const MaterialApp(
        home: FavoritesPage(),
      ),
    );

void addItems() {
  for (var i = 0; i < 10; i += 2) {
    favoritesList.add(i);
  }
}

void main() {
  group('Favorites Page Widget Tests', () {
    testWidgets('Test if ListView shows up', (tester) async {
      await tester.pumpWidget(createFavoritesScreen());
      addItems();
      await tester.pumpAndSettle();
      expect(find.byType(ListView), findsOneWidget);
    });

    testWidgets('Testing Remove Button', (tester) async {
      await tester.pumpWidget(createFavoritesScreen());
      addItems();
      await tester.pumpAndSettle();
      var totalItems = tester.widgetList(find.byIcon(Icons.close)).length;
      await tester.tap(find.byIcon(Icons.close).first);
      await tester.pumpAndSettle();
      expect(tester.widgetList(find.byIcon(Icons.close)).length,
          lessThan(totalItems));
      expect(find.text('Removed from favorites.'), findsOneWidget);
    });
  });
}

Pengujian ini memastikan apakah item menghilang ketika tombol tutup (hapus) ditekan.

Untuk informasi selengkapnya mengenai pengujian widget, kunjungi:

7. Menguji UI aplikasi dengan pengujian integrasi

Pengujian integrasi digunakan untuk menguji cara setiap bagian aplikasi berfungsi sebagai satu kesatuan. Library integration_test digunakan untuk melakukan pengujian integrasi di Flutter. Library ini adalah versi Flutter dari Selenium WebDriver, Protractor, Espresso, atau Earl Gray. Paket ini menggunakan flutter_driver secara internal untuk menjalankan pengujian di perangkat.

Menjalankan pengujian integrasi di Flutter mirip dengan menulis pengujian widget, hanya saja pengujian ini dijalankan di perangkat seluler, browser, atau aplikasi desktop, yang disebut perangkat target.

Menulis pengujian

a3c16fc17be25f6c.pngBuat direktori bernama integration_test di direktori utama project, lalu buat file baru bernama app_test.dart di direktori tersebut.

integration_test/app_test.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:testing_app/main.dart';

void main() {
  group('Testing App', () {
    testWidgets('Favorites operations test', (tester) async {
      await tester.pumpWidget(const TestingApp());

      final iconKeys = [
        'icon_0',
        'icon_1',
        'icon_2',
      ];

      for (var icon in iconKeys) {
        await tester.tap(find.byKey(ValueKey(icon)));
        await tester.pumpAndSettle(const Duration(seconds: 1));

        expect(find.text('Added to favorites.'), findsOneWidget);
      }

      await tester.tap(find.text('Favorites'));
      await tester.pumpAndSettle();

      final removeIconKeys = [
        'remove_icon_0',
        'remove_icon_1',
        'remove_icon_2',
      ];

      for (final iconKey in removeIconKeys) {
        await tester.tap(find.byKey(ValueKey(iconKey)));
        await tester.pumpAndSettle(const Duration(seconds: 1));

        expect(find.text('Removed from favorites.'), findsOneWidget);
      }
    });
  });
}

Menjalankan pengujian

a3c16fc17be25f6c.pngHubungkan perangkat ke sumber listrik atau mulai emulator Anda. Anda juga dapat menjalankan pengujian sebagai aplikasi desktop.

a3c16fc17be25f6c.pngDi command line, buka direktori utama project lalu masukkan perintah berikut:

$ flutter test integration_test/app_test.dart

Jika semuanya berfungsi, Anda akan melihat output mirip seperti ini:

Multiple devices found:
Linux (desktop) • linux  • linux-x64      • Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web)    • chrome • web-javascript • Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"): 1
00:00 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart                                                B00:08 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart
00:26 +1: All tests passed!

8. Menguji performa aplikasi dengan Flutter Driver

Menulis pengujian performa

Buat file pengujian baru bernama perf_test.dart dalam folder integration_test dengan konten berikut:

integration_test/perf_test.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:testing_app/main.dart';

void main() {
  group('Testing App Performance', () {
    final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;

    testWidgets('Scrolling test', (tester) async {
      await tester.pumpWidget(const TestingApp());

      final listFinder = find.byType(ListView);

      await binding.traceAction(() async {
        await tester.fling(listFinder, const Offset(0, -500), 10000);
        await tester.pumpAndSettle();

        await tester.fling(listFinder, const Offset(0, 500), 10000);
        await tester.pumpAndSettle();
      }, reportKey: 'scrolling_summary');
    });
  });
}

Fungsi ensureInitialized() memastikan apakah driver pengujian integrasi telah diinisialisasi, dan akan melakukan inisialisasi ulang jika perlu. Menyetel framePolicy sebagai fullyLive adalah langkah yang bagus untuk menguji kode animasi.

Pengujian ini men-scroll daftar item dengan sangat cepat, kemudian men-scroll ke paling atas. Fungsi traceAction() mencatat tindakan dan membuat ringkasan linimasa.

Mengambil hasil performa

Untuk mengambil hasil, buat folder bernama test_driver dengan file bernama perf_driver.dart, lalu tambahkan kode berikut:

test_driver/perf_driver.dart

import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';

Future<void> main() {
  return integrationDriver(
    responseDataCallback: (data) async {
      if (data != null) {
        final timeline = driver.Timeline.fromJson(
            data['scrolling_summary'] as Map<String, dynamic>);

        final summary = driver.TimelineSummary.summarize(timeline);

        await summary.writeTimelineToFile(
          'scrolling_summary',
          pretty: true,
          includeSummary: true,
        );
      }
    },
  );
}

Menjalankan pengujian

a3c16fc17be25f6c.pngHubungkan perangkat ke sumber listrik atau mulai emulator Anda.

a3c16fc17be25f6c.pngDi command line, buka direktori utama project lalu masukkan perintah berikut:

$ flutter drive \
  --driver=test_driver/perf_driver.dart \
  --target=integration_test/perf_test.dart \
  --profile \
  --no-dds

Jika semuanya berfungsi, Anda akan melihat output mirip seperti ini:

Running "flutter pub get" in testing_app...
Resolving dependencies...
  archive 3.3.2 (3.3.6 available)
  collection 1.17.0 (1.17.1 available)
  js 0.6.5 (0.6.7 available)
  matcher 0.12.13 (0.12.14 available)
  meta 1.8.0 (1.9.0 available)
  path 1.8.2 (1.8.3 available)
  test 1.22.0 (1.23.0 available)
  test_api 0.4.16 (0.4.18 available)
  test_core 0.4.20 (0.4.23 available)
  vm_service 9.4.0 (11.0.1 available)
  webdriver 3.0.1 (3.0.2 available)
Got dependencies!
Running Gradle task 'assembleProfile'...                         1,379ms
✓  Built build/app/outputs/flutter-apk/app-profile.apk (14.9MB).
Installing build/app/outputs/flutter-apk/app-profile.apk...        222ms
I/flutter ( 6125): 00:04 +1: Testing App Performance (tearDownAll)
I/flutter ( 6125): 00:04 +2: All tests passed!
All tests passed.

Setelah pengujian berhasil, direktori build di root project akan berisi dua file:

  1. scrolling_summary.timeline_summary.json berisi ringkasan. Buka file dengan editor teks apa pun untuk meninjau informasi yang ada di dalamnya.
  2. scrolling_summary.timeline.json berisi data linimasa lengkap.

Untuk detail selengkapnya terkait pengujian integrasi, buka:

9. Selamat!

Anda telah menyelesaikan codelab dan telah mempelajari berbagai cara untuk menguji aplikasi Flutter.

Yang telah Anda pelajari

  • Cara menguji penyedia dengan bantuan pengujian unit
  • Cara menguji widget menggunakan framework pengujian widget
  • Cara menguji UI aplikasi menggunakan pengujian integrasi
  • Cara menguji performa aplikasi menggunakan pengujian integrasi

Untuk mempelajari lebih lanjut pengujian di Flutter, buka