So testen Sie eine Flutter-App

1. Einführung

Flutter ist das UI-Toolkit von Google, mit dem Sie mit einer einzigen Codebasis ansprechende, nativ kompilierte Apps für Mobilgeräte, Web und Desktop erstellen können.

In diesem Codelab erstellen und testen Sie eine einfache Flutter-App. Die App verwendet das Paket Provider zur Verwaltung des Status.

Lerninhalte

  • Widget-Tests mit dem Widget-Test-Framework erstellen
  • Wie du einen Integrationstest erstellst, um die UI und Leistung der App mit der integration_test-Bibliothek zu testen
  • So testen Sie Datenklassen (Anbieter) mithilfe von Einheitentests

Aufgaben

In diesem Codelab erstellen Sie zuerst eine einfache Anwendung mit einer Liste von Elementen. Wir stellen Ihnen den Quellcode zur Verfügung, damit Sie direkt mit den Tests beginnen können. Die Anwendung unterstützt die folgenden Vorgänge:

  • Hinzufügen der Artikel zu Favoriten
  • Anzeigen der Favoritenliste
  • Elemente werden aus der Favoritenliste entfernt

Sobald die Anwendung fertig ist, schreiben Sie die folgenden Tests:

  • Unittests zum Prüfen der Vorgänge zum Hinzufügen und Entfernen
  • Widget-Tests für die Startseite und die Favoritenseiten
  • UI- und Leistungstests für die gesamte App mithilfe von Integrationstests

GIF der Android-App

Was möchten Sie in diesem Codelab lernen?

<ph type="x-smartling-placeholder"></ph> Ich kenne dieses Thema noch nicht und möchte einen guten Überblick erhalten. Ich weiß etwas über dieses Thema, möchte aber meine Kenntnisse auffrischen. Ich suche nach einem Beispielcode für mein Projekt. Ich suche nach einer Erklärung zu etwas Bestimmtem.

2. Flutter-Entwicklungsumgebung einrichten

Für dieses Lab benötigen Sie zwei Softwareprogramme: das Flutter SDK und einen Editor.

Sie können das Codelab auf jedem dieser Geräte ausführen:

  • Ein physisches Android- oder iOS, das mit Ihrem Computer verbunden ist und sich im Entwicklermodus befindet.
  • Den iOS-Simulator (erfordert die Installation von Xcode-Tools).
  • Android-Emulator (Einrichtung in Android Studio erforderlich)
  • Ein Browser (zur Fehlerbehebung wird Chrome benötigt)
  • Als Windows-, Linux- oder macOS-Desktopanwendung Die Entwicklung muss auf der Plattform erfolgen, auf der Sie die Bereitstellung planen. Wenn Sie also eine Windows-Desktop-App entwickeln möchten, müssen Sie die Entwicklung unter Windows ausführen, damit Sie auf die entsprechende Build-Kette zugreifen können. Es gibt betriebssystemspezifische Anforderungen, die unter docs.flutter.dev/desktop ausführlich beschrieben werden.

3. Erste Schritte

Neue Flutter-App erstellen und Abhängigkeiten aktualisieren

In diesem Codelab geht es um das Testen einer mobilen Flutter-App. Sie erstellen die zu testende Anwendung schnell mit Quelldateien, die Sie kopieren und einfügen. Im restlichen Codelab lernen Sie dann verschiedene Arten von Tests kennen.

a3c16fc17be25f6c.pngErstellen Sie eine einfache, aus Vorlagen erstellte Flutter-App. Folgen Sie dazu entweder der Anleitung unter Erste Schritte mit Ihrer ersten Flutter-App oder folgen Sie der Anleitung in der Befehlszeile.

$ flutter create --empty testing_app
Creating project testing_app...
Resolving dependencies in `testing_app`... 
Downloading packages... 
Got dependencies in `testing_app`.
Wrote 128 files.

All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

In order to run your empty application, type:

  $ cd testing_app
  $ flutter run

Your empty application code is in testing_app/lib/main.dart.

a3c16fc17be25f6c.pngPub-Abhängigkeiten in der Befehlszeile hinzufügen

  • provider für eine einfache Statusverwaltung,
  • integration_test zum selbstfahrenden Testen von Flutter-Code auf Geräten und Emulatoren
  • flutter_driver für eine erweiterte API zum Testen von Flutter-Anwendungen, die auf echten Geräten und Emulatoren ausgeführt werden,
  • test für allgemeine Testtools
  • go_router für die App-Navigation
$ cd testing_app
$ flutter pub add provider go_router dev:test 'dev:flutter_driver:{"sdk":"flutter"}' 'dev:integration_test:{"sdk":"flutter"}'
Resolving dependencies... 
Downloading packages... 
+ _fe_analyzer_shared 67.0.0 (68.0.0 available)
+ analyzer 6.4.1 (6.5.0 available)
+ args 2.5.0
+ convert 3.1.1
+ coverage 1.7.2
+ crypto 3.0.3
+ file 7.0.0
+ flutter_driver 0.0.0 from sdk flutter
+ flutter_web_plugins 0.0.0 from sdk flutter
+ frontend_server_client 4.0.0
+ fuchsia_remote_debug_protocol 0.0.0 from sdk flutter
+ glob 2.1.2
+ go_router 14.0.2
+ http_multi_server 3.2.1
+ http_parser 4.0.2
+ integration_test 0.0.0 from sdk flutter
+ io 1.0.4
+ js 0.7.1
  leak_tracker 10.0.4 (10.0.5 available)
  leak_tracker_flutter_testing 3.0.3 (3.0.5 available)
+ logging 1.2.0
  material_color_utilities 0.8.0 (0.11.1 available)
  meta 1.12.0 (1.14.0 available)
+ mime 1.0.5
+ nested 1.0.0
+ node_preamble 2.0.2
+ package_config 2.1.0
+ platform 3.1.4
+ pool 1.5.1
+ process 5.0.2
+ provider 6.1.2
+ pub_semver 2.1.4
+ shelf 1.4.1
+ shelf_packages_handler 3.0.2
+ shelf_static 1.1.2
+ shelf_web_socket 1.0.4
+ source_map_stack_trace 2.1.1
+ source_maps 0.10.12
+ sync_http 0.3.1
+ test 1.25.2 (1.25.4 available)
  test_api 0.7.0 (0.7.1 available)
+ test_core 0.6.0 (0.6.2 available)
+ typed_data 1.3.2
+ watcher 1.1.0
+ web 0.5.1
+ web_socket_channel 2.4.5
+ webdriver 3.0.3
+ webkit_inspection_protocol 1.2.1
+ yaml 3.1.2
Changed 44 dependencies!
9 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

Die folgenden Abhängigkeiten sollten Ihrer pubspec.yaml hinzugefügt worden sein:

pubspec.yaml

name: testing_app
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0

environment:
  sdk: '>=3.4.0-0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  go_router: ^14.0.2
  provider: ^6.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.0
  test: ^1.25.2
  flutter_driver:
    sdk: flutter
  integration_test:
    sdk: flutter

flutter:
  uses-material-design: true

a3c16fc17be25f6c.pngÖffnen Sie das Projekt in einem Codeeditor Ihrer Wahl und führen Sie die App aus. Alternativ können Sie ihn wie folgt in der Befehlszeile ausführen.

$ flutter run

4. App erstellen

Als Nächstes erstellen Sie die App, um sie zu testen. Die Anwendung enthält die folgenden Dateien:

  • lib/models/favorites.dart – erstellt die Modellklasse für die Favoritenliste
  • lib/screens/favorites.dart erstellt das Layout für die Favoritenliste.
  • lib/screens/home.dart: Erstellt eine Liste von Elementen
  • lib/main.dart – die Hauptdatei, in der die Anwendung gestartet wird

Erstellen Sie zuerst das Modell Favorites in lib/models/favorites.dart

a3c16fc17be25f6c.pngErstellen Sie im Verzeichnis lib ein neues Verzeichnis mit dem Namen models und erstellen Sie dann eine neue Datei mit dem Namen favorites.dart. Fügen Sie in diese Datei den folgenden Code ein:

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

Fügen Sie die Favoritenseite in lib/screens/favorites.dart hinzu

a3c16fc17be25f6c.pngErstellen Sie im Verzeichnis lib ein neues Verzeichnis mit dem Namen screens und dort eine neue Datei mit dem Namen favorites.dart. Fügen Sie in diese Datei den folgenden Code ein:

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

Startseite in lib/screens/home.dart hinzufügen

a3c16fc17be25f6c.pngErstellen Sie im Verzeichnis lib/screens eine weitere neue Datei mit dem Namen home.dart. Fügen Sie in lib/screens/home.dart den folgenden Code ein:

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

Inhalt von lib/main.dart ersetzen

a3c16fc17be25f6c.pngErsetzen Sie den Inhalt von lib/main.dart durch den folgenden Code:

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(
          colorScheme: ColorScheme.fromSeed(
            seedColor: Colors.deepPurple,
          ),
          useMaterial3: true,
        ),
        routerConfig: _router,
      ),
    );
  }
}

Die Anwendung ist jetzt vollständig, wurde aber noch nicht getestet.

a3c16fc17be25f6c.pngApp ausführen Er sollte so aussehen:

b74f843e42a28b0f.png

Die App zeigt eine Liste mit Artikeln an. Tippe in einer beliebigen Zeile auf das herzförmige Symbol, um das Herz zu füllen und den Artikel deiner Favoritenliste hinzuzufügen. Über die Schaltfläche Favoriten auf dem AppBar gelangst du zu einem zweiten Bildschirm mit der Favoritenliste.

Die App kann jetzt getestet werden. Im nächsten Schritt beginnen Sie mit dem Testen der App.

5. Unittest des Anbieters

Sie beginnen mit einem Einheitentest des favorites-Modells. Was ist ein Unittest? Ein Unittest überprüft, ob jede einzelne Softwareeinheit, ob eine Funktion, ein Objekt oder ein Widget, die beabsichtigte Aufgabe ordnungsgemäß ausführt.

Alle Testdateien in einer Flutter-Anwendung, mit Ausnahme von Integrationstests, werden im Verzeichnis test abgelegt.

test/widget_test.dart entfernen

a3c16fc17be25f6c.pngLöschen Sie die Datei widget_test.dart, bevor Sie mit dem Testen beginnen. Sie fügen Ihre eigenen Testdateien hinzu.

Neue Testdatei erstellen

Zuerst testen Sie die Methode add() im Modell Favorites, um zu prüfen, ob der Liste ein neues Element hinzugefügt wird und ob die Änderung in der Liste berücksichtigt wird. Konventionsgemäß entspricht die Verzeichnisstruktur im Verzeichnis test der im Verzeichnis lib und in den Dart-Dateien, die denselben Namen haben, wobei _test angehängt ist.

a3c16fc17be25f6c.pngErstellen Sie im Verzeichnis test ein Verzeichnis models. Erstellen Sie in diesem neuen Verzeichnis eine favorites_test.dart-Datei mit folgendem Inhalt:

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

Mit dem Flutter-Test-Framework können Sie ähnliche Tests, die zueinander in Beziehung stehen, in einer Gruppe zusammenfassen. Eine Testdatei kann mehrere Gruppen enthalten, mit denen verschiedene Teile der entsprechenden Datei im Verzeichnis lib getestet werden sollen.

Die Methode test() verwendet zwei Positionsparameter: den description des Tests und den callback, in den Sie den Test schreiben.

a3c16fc17be25f6c.pngEinen Artikel aus der Liste entfernen (Test) Fügen Sie den folgenden Test in dieselbe Testing App Provider-Gruppe ein:

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

Test durchführen

a3c16fc17be25f6c.pngGehen Sie in der Befehlszeile zum Stammverzeichnis des Projekts und geben Sie den folgenden Befehl ein:

$ flutter test test/models/favorites_test.dart 

Wenn alles funktioniert, sollten Sie eine Meldung wie diese sehen:

00:06 +2: All tests passed!                                                    

Die vollständige Testdatei: test/models/favorites_test.dart.

Weitere Informationen zu Einheitentests finden Sie in der Einführung in Einheitentests.

6. Widget-Tests

In diesem Schritt fügen Sie Code zum Testen von Widgets hinzu. Widget-Tests gibt es nur bei Flutter. Hier können Sie jedes Widget isoliert testen. In diesem Schritt werden die Bildschirme HomePage und FavoritesPage einzeln getestet.

Für Widget-Tests wird die Funktion testWidget() anstelle der Funktion test() verwendet. Wie die test()-Funktion verwendet auch die testWidget()-Funktion zwei Parameter: description, und callback. Der Callback übernimmt jedoch WidgetTester als Argument.

Widget-Tests verwenden TestFlutterWidgetsBinding.Diese Klasse stellt für Ihre Widgets dieselben Ressourcen bereit wie in einer laufenden App. Beispiel: Informationen zur Bildschirmgröße und zur Planung von Animationen, die nicht in einer App ausgeführt werden müssen. Stattdessen wird eine virtuelle Umgebung verwendet, um das Widget zu instanziieren und anschließend die Ergebnisse zu testen. Hier startet pumpWidget den Prozess und weist das Framework an, ein bestimmtes Widget wie in einer Anwendung bereitzustellen und zu messen.

Mit dem Widget-Test-Framework können Finder Widgets finden, z. B. text(), byType() und byIcon().. Das Framework bietet auch Abgleichstools, um die Ergebnisse zu prüfen.

Testen Sie zuerst das HomePage-Widget.

Neue Testdatei erstellen

Mit dem ersten Test wird überprüft, ob das Scrollen der HomePage ordnungsgemäß funktioniert.

a3c16fc17be25f6c.pngErstellen Sie im Verzeichnis test eine neue Datei und nennen Sie sie home_test.dart. Fügen Sie der neu erstellten Datei den folgenden Code hinzu:

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

Mit der Funktion createHomeScreen() wird eine App erstellt, die das zu testende Widget in einer MaterialApp lädt, eingebunden in einen ChangeNotifierProvider. Für das Startseiten-Widget müssen beide Widgets in der Widget-Baumstruktur darüber vorhanden sein, damit es die Widgets übernehmen und Zugriff auf die angebotenen Daten erhalten kann. Diese Funktion wird als Parameter an die Funktion pumpWidget() übergeben.

Testen Sie als Nächstes, ob das Framework ein auf dem Bildschirm gerendertes ListView finden kann.

a3c16fc17be25f6c.pngFügen Sie home_test.dart das folgende Code-Snippet hinzu:

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

Test durchführen

Führen Sie zunächst den Test auf dieselbe Weise durch wie einen Einheitentest. Verwenden Sie folgenden Befehl:

$ flutter test test/home_test.dart 

Der Test sollte schnell ausgeführt werden und Sie sollten eine Meldung wie die folgende sehen:

00:02 +2: All tests passed!                                                    

Sie können Widget-Tests auch mit einem Gerät oder einem Emulator ausführen, um den laufenden Test mitzuverfolgen. Sie können auch einen Heißneustart verwenden.

a3c16fc17be25f6c.pngSchließen Sie Ihr Gerät an die Stromversorgung an oder starten Sie den Emulator. Sie können den Test auch als Desktopanwendung ausführen.

a3c16fc17be25f6c.pngGehen Sie über die Befehlszeile zum Stammverzeichnis des Projekts und geben Sie den folgenden Befehl ein:

$ flutter run test/home_test.dart 

Möglicherweise musst du das Gerät auswählen, auf dem der Test ausgeführt werden soll. Folgen Sie in diesem Fall der Anleitung und wählen Sie ein Gerät aus:

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"): 

Wenn alles funktioniert, sollte die Ausgabe in etwa so aussehen:

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!

Als Nächstes nehmen Sie Änderungen an der Testdatei vor und drücken Shift + R, um die App neu zu starten und alle Tests noch einmal auszuführen. Beenden Sie die Anwendung nicht.

a3c16fc17be25f6c.pngFügen Sie der Gruppe, die die Startseiten-Widgets testet, weitere Tests hinzu. Kopieren Sie den folgenden Test in Ihre Datei:

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

Bei diesem Test wird überprüft, ob sich das Antippen des IconButton von Icons.favorite_border (offenes Herz) zu Icons.favorite (ein ausgefülltes Herz) und dann wieder zurück zu Icons.favorite_border ändert, wenn es noch einmal angetippt wird.

a3c16fc17be25f6c.pngGeben Sie Shift + R ein. Dadurch wird die App neu gestartet und alle Tests werden noch einmal ausgeführt.

Die vollständige Testdatei: test/home_test.dart.

a3c16fc17be25f6c.pngGehen Sie genauso vor, um die FavoritesPage mit dem folgenden Code zu testen. Führen Sie dieselben Schritte aus und führen Sie es aus.

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

Dieser Test überprüft, ob ein Element verschwindet, wenn die Schaltfläche zum Schließen (Entfernen) gedrückt wird.

Weitere Informationen zu Widget-Tests finden Sie unter:

7. App-UI mit Integrationstests testen

Integrationstests werden verwendet, um zu testen, wie die einzelnen Teile einer App als Ganzes zusammenarbeiten. Die Bibliothek integration_test wird verwendet, um Integrationstests in Flutter durchzuführen. Dies ist die Flutter-Version von Selenium WebDriver, Protraktor, Espresso oder Earl Gray. Das Paket verwendet intern flutter_driver, um den Test auf einem Gerät durchzuführen.

Das Schreiben von Integrationstests in Flutter ähnelt dem Schreiben von Widgettests, mit der Ausnahme, dass Integrationstests auf einem Mobilgerät, in einem Browser oder in einer Desktopanwendung ausgeführt werden, dem Zielgerät.

Test schreiben

a3c16fc17be25f6c.pngErstellen Sie im Stammverzeichnis des Projekts ein Verzeichnis mit dem Namen integration_test und dort eine neue Datei mit dem Namen app_test.dart.

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

Test durchführen

a3c16fc17be25f6c.pngSchließen Sie Ihr Gerät an die Stromversorgung an oder starten Sie den Emulator. Sie können den Test auch als Desktopanwendung ausführen.

a3c16fc17be25f6c.pngGehen Sie in der Befehlszeile zum Stammverzeichnis des Projekts und geben Sie den folgenden Befehl ein:

$ flutter test integration_test/app_test.dart

Wenn alles funktioniert, sollte die Ausgabe in etwa so aussehen:

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. App-Leistung mit Flutter Driver testen

Leistungstest schreiben

Erstellen Sie im Ordner integration_test eine neue Testdatei mit dem Namen perf_test.dart mit folgendem Inhalt:

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

Die Funktion ensureInitialized() prüft, ob der Integrationstesttreiber initialisiert wurde, und initialisiert ihn gegebenenfalls neu. Das Festlegen von framePolicy auf fullyLive eignet sich gut zum Testen von animiertem Code.

Bei diesem Test wird sehr schnell durch die Liste der Artikel und dann ganz nach oben gescrollt. Mit der Funktion traceAction() werden die Aktionen aufgezeichnet und eine Zusammenfassung des Zeitplans generiert.

Leistungsergebnisse erfassen

Erstellen Sie zum Erfassen der Ergebnisse einen Ordner namens test_driver mit einer Datei namens perf_driver.dart und fügen Sie den folgenden Code hinzu:

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

Test durchführen

a3c16fc17be25f6c.pngSchließen Sie Ihr Gerät an die Stromversorgung an oder starten Sie den Emulator.

a3c16fc17be25f6c.pngGehen Sie in der Befehlszeile zum Stammverzeichnis des Projekts und geben Sie den folgenden Befehl ein:

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

Wenn alles funktioniert, sollte die Ausgabe in etwa so aussehen:

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.

Nach erfolgreichem Abschluss des Tests enthält das Build-Verzeichnis im Stammverzeichnis des Projekts zwei Dateien:

  1. scrolling_summary.timeline_summary.json enthält die Zusammenfassung. Öffnen Sie die Datei mit einem Texteditor, um sich die darin enthaltenen Informationen anzusehen.
  2. scrolling_summary.timeline.json enthält die vollständigen Zeitachsendaten.

Weitere Informationen zu Integrationstests finden Sie hier:

9. Glückwunsch!

Sie haben das Codelab abgeschlossen und verschiedene Möglichkeiten zum Testen einer Flutter-App kennengelernt.

Das haben Sie gelernt

  • Anbieter mithilfe von Einheitentests testen
  • Widgets mit dem Widget-Test-Framework testen
  • So testen Sie die UI der App mithilfe von Integrationstests
  • So testen Sie die Leistung der App mithilfe von Integrationstests

Weitere Informationen zu Tests in Flutter finden Sie unter