Come testare un'app Flutter

1. Introduzione

Flutter è il toolkit dell'interfaccia utente di Google che consente di creare fantastiche applicazioni compilate in modo nativo per dispositivi mobili, web e computer a partire da un unico codebase.

In questo codelab, creerai e testerai una semplice app Flutter. L'app utilizzerà il pacchetto Provider per la gestione dello stato.

Obiettivi didattici

  • Come creare test dei widget utilizzando il framework di test dei widget
  • Come creare un test di integrazione per testare l'UI e le prestazioni dell'app utilizzando la libreria integration_test
  • Come testare le classi di dati (fornitori) con l'aiuto dei test delle unità

Cosa creerai

In questo codelab, inizierai a creare una semplice applicazione con un elenco di elementi. Il codice sorgente ti viene fornito in modo che tu possa passare direttamente al test. L'app supporta le seguenti operazioni:

  • Aggiunta degli elementi ai preferiti in corso...
  • Visualizzazione dell'elenco dei preferiti
  • Rimozione di elementi dall'elenco dei preferiti

Una volta completata l'app, scriverai i seguenti test:

  • Test delle unità per convalidare le operazioni di aggiunta e rimozione
  • Test dei widget per la home page e le pagine preferite
  • Test delle prestazioni e dell'interfaccia utente per l'intera app tramite i test di integrazione

GIF dell'app in esecuzione su Android

Cosa ti piacerebbe imparare da questo codelab?

Non ho mai affrontato questo argomento e vorrei una panoramica completa. So qualcosa su questo argomento, ma vorrei rinfrescarti un po'. Sto cercando un codice di esempio da utilizzare nel mio progetto. Vorrei avere una spiegazione su qualcosa di specifico.

2. Configura l'ambiente di sviluppo di Flutter

Per completare questo lab sono necessari due software: l'SDK Flutter e l'editor.

Puoi eseguire il codelab utilizzando uno di questi dispositivi:

  • Un dispositivo fisico Android o iOS connesso al computer e impostato sulla modalità sviluppatore.
  • Il simulatore iOS (richiede l'installazione degli strumenti Xcode).
  • L'emulatore Android (richiede la configurazione in Android Studio).
  • Un browser (per il debug è richiesto Chrome).
  • Come applicazione desktop Windows, Linux o macOS. Devi svilupparle sulla piattaforma in cui prevedi di eseguire il deployment. Quindi, se vuoi sviluppare un'app desktop per Windows, devi sviluppare su Windows per accedere alla catena di build appropriata. Alcuni requisiti specifici del sistema operativo sono descritti in dettaglio all'indirizzo docs.flutter.dev/desktop.

3. Per iniziare

Creare una nuova app Flutter e aggiorna le dipendenze

Questo codelab è incentrato sul test di un'app mobile Flutter. Creerai rapidamente l'app da testare utilizzando file di origine che copi e incolli. Il resto del codelab è incentrato sull'apprendimento di diversi tipi di test.

a3c16fc17be25f6c.pngCrea una semplice app Flutter basata su modelli, seguendo le istruzioni riportate in Iniziare a usare la tua prima app Flutter o utilizzando la riga di comando, come mostrato di seguito.

$ 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.pngAggiungi dipendenze Pub alla riga di comando.

  • provider per gestire facilmente lo stato,
  • integration_test per testare in modo autonomo il codice Flutter su dispositivi ed emulatori,
  • flutter_driver per un'API avanzata per testare le applicazioni Flutter eseguite su emulatori e dispositivi reali
  • test per gli strumenti di test generali,
  • go_router per gestire la navigazione delle app.
$ 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.

Le seguenti dipendenze dovrebbero essere state aggiunte a pubspec.yaml:

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.pngApri il progetto nell'editor di codice che preferisci ed esegui l'app. In alternativa, eseguilo dalla riga di comando come segue.

$ flutter run

4. Crea l'app

Adesso devi creare l'app per testarla. L'app contiene i seguenti file:

  • lib/models/favorites.dart: crea la classe del modello per l'elenco dei preferiti
  • lib/screens/favorites.dart: crea il layout per l'elenco dei preferiti
  • lib/screens/home.dart: crea un elenco di elementi
  • lib/main.dart: il file principale in cui viene avviata l'app.

Innanzitutto, crea il modello Favorites in lib/models/favorites.dart

a3c16fc17be25f6c.pngCrea una nuova directory denominata models nella directory lib, quindi crea un nuovo file denominato favorites.dart. Aggiungi il codice seguente al file:

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

Aggiungi la pagina Preferiti in lib/screens/favorites.dart

a3c16fc17be25f6c.pngCrea una nuova directory denominata screens nella directory lib e in quella directory crea un nuovo file denominato favorites.dart. Aggiungi il codice seguente al file:

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

Aggiungi la home page in lib/screens/home.dart

a3c16fc17be25f6c.pngNella directory lib/screens crea un altro nuovo file denominato home.dart. In lib/screens/home.dart, aggiungi il seguente codice:

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

Sostituisci i contenuti di lib/main.dart

a3c16fc17be25f6c.pngSostituisci i contenuti di lib/main.dart con il seguente codice:

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

Ora l'app è completa, ma non è stata testata.

a3c16fc17be25f6c.pngEsegui l'app. Dovrebbe essere simile al seguente screenshot:

b74f843e42a28b0f.png

L'app mostra un elenco di elementi. Tocca l'icona a forma di cuore su una riga qualsiasi per riempire il cuore e aggiungere l'elemento all'elenco dei preferiti. Il pulsante Preferiti su AppBar ti porta a una seconda schermata contenente l'elenco dei preferiti.

L'app è ora pronta per i test. Inizierai a testare l'app nel passaggio successivo.

5. Test delle unità del provider

Inizierai eseguendo il test delle unità del modello favorites. Che cos'è un test delle unità? Un test delle unità verifica che ogni singola unità del software, che si tratti di una funzione, di un oggetto o di un widget, esegue correttamente l'attività prevista.

Tutti i file di test in un'app Flutter, ad eccezione dei test di integrazione, vengono inseriti nella directory test.

Rimuovi test/widget_test.dart

a3c16fc17be25f6c.pngPrima di iniziare il test, elimina il file widget_test.dart. Aggiungerai i tuoi file di test.

Crea un nuovo file di test

Innanzitutto, testerai il metodo add() nel modello Favorites per verificare che un nuovo elemento venga aggiunto all'elenco e che l'elenco rifletta la modifica. Per convenzione, la struttura di directory nella directory test riproduce il nome della directory lib e dei file Dart, con l'aggiunta di _test.

a3c16fc17be25f6c.pngCrea una directory models nella directory test. In questa nuova directory, crea un file favorites_test.dart con i seguenti contenuti:

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

Il framework di test Flutter ti consente di associare test simili correlati tra loro in un gruppo. Un singolo file di test può contenere più gruppi con lo scopo di testare diverse parti del file corrispondente nella directory lib.

Il metodo test() accetta due parametri posizionali: description del test e callback in cui scrivi effettivamente il test.

a3c16fc17be25f6c.pngProva a rimuovere un elemento dall'elenco. Inserisci il seguente test nello stesso gruppo Testing App Provider:

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

Eseguire il test

a3c16fc17be25f6c.pngNella riga di comando, vai alla directory principale del progetto e inserisci questo comando:

$ flutter test test/models/favorites_test.dart 

Se tutto funziona correttamente, dovresti vedere un messaggio simile al seguente:

00:06 +2: All tests passed!                                                    

Il file di test completo: test/models/favorites_test.dart.

Per ulteriori informazioni sui test delle unità, consulta Introduzione ai test delle unità.

6. Test dei widget

In questo passaggio aggiungerai codice per testare i widget. Il test dei widget è un'esclusiva di Flutter, che consente di testare ogni widget in modo isolato. Questo passaggio verifica le schermate HomePage e FavoritesPage singolarmente.

Il test del widget utilizza la funzione testWidget() anziché la funzione test(). Come la funzione test(), la funzione testWidget() accetta due parametri: description, e callback, ma il callback utilizza WidgetTester come argomento.

I test dei widget utilizzano TestFlutterWidgetsBinding, una classe che fornisce ai tuoi widget le stesse risorse che avrebbero in un'app in esecuzione, ad esempio informazioni sulle dimensioni dello schermo e sulla possibilità di pianificare le animazioni, ma senza eseguire l'operazione all'interno di un'app. Viene invece utilizzato un ambiente virtuale per creare un'istanza del widget e testare i risultati. In questo caso, pumpWidget avvia il processo dicendo al framework di montare e misurare un determinato widget proprio come farebbe in un'applicazione.

Il framework di test del widget fornisce agli utenti che trovano widget, ad esempio text(), byType() e byIcon().. Inoltre, fornisce matcher per verificare i risultati.

Inizia provando il widget HomePage.

Crea un nuovo file di test

Il primo test verifica se lo scorrimento di HomePage funziona correttamente.

a3c16fc17be25f6c.pngCrea un nuovo file nella directory test e assegnagli il nome home_test.dart. Nel file appena creato, aggiungi il codice seguente:

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

La funzione createHomeScreen() viene utilizzata per creare un'app che carica il widget da testare in un MaterialApp, aggregato in un ChangeNotifierProvider. Il widget della home page richiede che entrambi questi widget siano presenti sopra nella struttura ad albero dei widget, in modo che possa ereditare da questi e ottenere l'accesso ai dati che offrono. Questa funzione viene passata come parametro alla funzione pumpWidget().

Ora verifica se il framework riesce a trovare un ListView visualizzato sullo schermo.

a3c16fc17be25f6c.pngAggiungi il seguente snippet di codice a 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);
    });
});

Eseguire il test

Per prima cosa, esegui il test nello stesso modo in cui eseguiresti un test delle unità, con il comando:

$ flutter test test/home_test.dart 

Il test dovrebbe essere eseguito rapidamente e dovresti visualizzare un messaggio simile al seguente:

00:02 +2: All tests passed!                                                    

Puoi anche eseguire test del widget utilizzando un dispositivo o un emulatore, che ti consente di osservare l'esecuzione del test. Inoltre, ti offre la possibilità di utilizzare il riavvio a caldo.

a3c16fc17be25f6c.pngCollega il dispositivo o avvia l'emulatore. Puoi anche eseguire il test come applicazione desktop.

a3c16fc17be25f6c.pngDalla riga di comando, vai alla directory principale del progetto e inserisci questo comando:

$ flutter run test/home_test.dart 

Potresti dover selezionare il dispositivo su cui eseguire il test. In questo caso, segui le istruzioni e seleziona un dispositivo:

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

Se tutto funziona, dovresti vedere un output simile al seguente:

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!

Successivamente, apporterai le modifiche al file di test e premi Shift + R per riavviare l'app a caldo e ripetere tutti i test. Non arrestare l'applicazione.

a3c16fc17be25f6c.pngAggiungi altri test al gruppo che verifica i widget della home page. Copia il seguente test nel tuo file:

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

Questo test verifica che il tocco del IconButton passi da Icons.favorite_border (un cuore aperto) a Icons.favorite (un cuore pieno) e poi di nuovo a Icons.favorite_border quando viene toccato di nuovo.

a3c16fc17be25f6c.pngInserisci Shift + R. Questo aggiornamento rapido riavvia l'app ed esegue nuovamente tutti i test.

Il file di test completo: test/home_test.dart.

a3c16fc17be25f6c.pngUsa la stessa procedura per testare FavoritesPage con il codice che segue. Segui gli stessi passaggi ed eseguilo.

test/Favorites_test.ARROW

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

Questo test verifica se un elemento scompare quando viene premuto il pulsante di chiusura (rimozione).

Per ulteriori informazioni sul test del widget, visita:

7. Test dell'interfaccia utente dell'app con test di integrazione

I test di integrazione vengono utilizzati per verificare l'interazione tra le singole parti di un'app. La libreria integration_test viene utilizzata per eseguire i test di integrazione in Flutter. Questa è la versione di Flutter di Selenium WebDriver, Goniometro, Espresso o Earl Gray. Il pacchetto utilizza internamente flutter_driver per eseguire il test su un dispositivo.

Scrivere test di integrazione in Flutter è simile alla scrittura di test widget, ad eccezione del fatto che i test di integrazione vengono eseguiti su un dispositivo mobile, un browser o un'applicazione desktop, chiamato dispositivo di destinazione.

Scrivi il test

a3c16fc17be25f6c.pngCrea una directory denominata integration_test nella directory radice del progetto e in quella directory crea un nuovo file denominato 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);
      }
    });
  });
}

Eseguire il test

a3c16fc17be25f6c.pngCollega il dispositivo o avvia l'emulatore. Puoi anche eseguire il test come applicazione desktop.

a3c16fc17be25f6c.pngNella riga di comando, vai alla directory principale del progetto e inserisci questo comando:

$ flutter test integration_test/app_test.dart

Se tutto funziona, dovresti vedere un output simile al seguente:

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. Testare le prestazioni dell'app con Flutter Driver

Scrivere un test delle prestazioni

Crea un nuovo file di test denominato perf_test.arrow nella cartella integration_test con il contenuto seguente:

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

La funzione ensureInitialized() verifica se il driver del test di integrazione è stato inizializzato, reinizializzandolo se necessario. Impostare il valore framePolicy su fullyLive è ideale per testare il codice animato.

Questo test scorre molto velocemente l'elenco di elementi e poi scorre completamente verso l'alto. La funzione traceAction() registra le azioni e genera un riepilogo della sequenza temporale.

Acquisisci i risultati sul rendimento

Per acquisire i risultati, crea una cartella denominata test_driver con un file denominato perf_driver.dart e aggiungi il codice seguente:

test_driver/perf_driver.arrow

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

Eseguire il test

a3c16fc17be25f6c.pngCollega il dispositivo o avvia l'emulatore.

a3c16fc17be25f6c.pngNella riga di comando, vai alla directory principale del progetto e inserisci questo comando:

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

Se tutto funziona, dovresti vedere un output simile al seguente:

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.

Al termine del test, la directory di compilazione alla radice del progetto contiene due file:

  1. scrolling_summary.timeline_summary.json contiene il riepilogo. Apri il file con qualsiasi editor di testo per esaminare le informazioni al suo interno.
  2. scrolling_summary.timeline.json contiene i dati completi della sequenza temporale.

Per maggiori dettagli sui test di integrazione, visita:

9. Complimenti!

Hai completato il codelab e hai imparato diversi modi per testare un'app Flutter.

Che cosa hai imparato

  • Come testare i fornitori con l'aiuto dei test delle unità
  • Come testare i widget utilizzando il framework di test dei widget
  • Come testare l'UI dell'app utilizzando i test di integrazione
  • Come testare le prestazioni dell'app utilizzando i test di integrazione

Per scoprire di più sui test in Flutter, visita