Cómo probar una app de Flutter

1. Introducción

Flutter es el kit de herramientas de IU de Google diseñado para crear aplicaciones atractivas compiladas de forma nativa que funcionen en dispositivos móviles, la Web y computadoras de escritorio a partir de una base de código única.

En este codelab, compilarás y probarás una app de Flutter simple. La app usará el paquete Provider para administrar el estado.

Qué aprenderás

  • Cómo crear pruebas de widgets con el framework de prueba del widget
  • Cómo crear una prueba de integración para probar la IU y el rendimiento de la app con la biblioteca integration_test
  • Cómo probar clases de datos (proveedores) con la ayuda de las pruebas de unidades

Qué compilarás

En este codelab, comenzarás a compilar una aplicación simple con una lista de elementos. Te proporcionamos el código fuente para que puedas comenzar directamente con las pruebas. La aplicación admite las siguientes operaciones:

  • Cómo agregar los elementos a favoritos
  • Cómo ver la lista de favoritos
  • Cómo quitar elementos de la lista de favoritos

Una vez que la app esté completa, escribirás las siguientes pruebas:

  • Pruebas de unidades para validar las operaciones de agregar y quitar
  • Pruebas de widgets para la página principal y de favoritos
  • Pruebas de IU y rendimiento para toda la app con pruebas de integración

GIF de la app que se ejecuta en Android

¿Qué te gustaría aprender de este codelab?

Desconozco el tema y me gustaría obtener una buena descripción general. Tengo algunos conocimientos sobre este tema, pero me gustaría repasarlos. Estoy buscando un código de ejemplo para usar en mi proyecto. Estoy buscando una explicación sobre un tema específico.

2. Configura tu entorno de desarrollo de Flutter

Para completar este codelab, necesitas dos tipos de software: el SDK de Flutter y un editor.

Puedes ejecutar el codelab con cualquiera de estos dispositivos o modalidades:

  • Un dispositivo físico Android o iOS conectado a tu computadora y configurado en el modo de desarrollador
  • El simulador de iOS (requiere la instalación de herramientas de Xcode)
  • Android Emulator (requiere configuración en Android Studio)
  • Un navegador (para la depuración, se requiere Chrome)
  • Una aplicación para computadoras que se ejecute en Windows, Linux o macOS (debes desarrollar contenido en la plataforma donde tengas pensado realizar la implementación. Por lo tanto, si quieres desarrollar una app de escritorio para Windows, debes desarrollarla en ese SO para obtener acceso a la cadena de compilación correcta. Encuentra detalles sobre los requisitos específicos del sistema operativo en docs.flutter.dev/desktop)

3. Primeros pasos

Cómo crear una app de Flutter nueva y actualizar las dependencias

Este codelab se enfoca en la prueba de una app para dispositivos móviles de Flutter. Crearás rápidamente la app que deseas probar con los archivos fuente que copias y pegas. El resto del codelab se enfoca en aprender diferentes tipos de pruebas.

a3c16fc17be25f6c.pngCrea una app de Flutter simple a partir de una plantilla, siguiendo las instrucciones en Cómo comenzar a crear tu primera app de Flutter o mediante la línea de comandos como se indica a continuación.

$ flutter create testing_app

a3c16fc17be25f6c.pngAgrega las dependencias pub en la línea de comandos. Para facilitar la gestión del estado, agrega 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!

Para realizar pruebas automáticas del código de Flutter en dispositivos y emuladores, agrega 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!

Para obtener una API avanzada para probar aplicaciones de Flutter que se ejecutan en emuladores y dispositivos reales, agrega 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!

Para obtener herramientas de pruebas generales, agrega 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!

Para controlar la navegación de la app, agrega 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!

Se deberían haber agregado las siguientes dependencias al archivo pubspec.yaml:

En dependencies:

dependencies:
  provider: ^6.0.5
  go_router: ^6.0.4

En dev_dependencies:

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

a3c16fc17be25f6c.pngAbre el proyecto en el editor de código que prefieras y ejecuta la app. De manera opcional, puedes ejecutarla en la línea de comandos como se indica a continuación.

$ flutter run

4. Compila la app

Luego, compilarás la app para que puedas probarla. La app contiene los siguientes archivos:

  • lib/models/favorites.dart: Crea la clase del modelo para la lista de favoritos.
  • lib/screens/favorites.dart: Crea el diseño para la lista de favoritos.
  • lib/screens/home.dart: Crea una lista de elementos.
  • lib/main.dart: Es el archivo principal donde se inicia la app.

Primero, crea el modelo de Favorites en lib/models/favorites.dart

a3c16fc17be25f6c.pngCrea un directorio nuevo llamado models en el directorio lib y, luego, crea un archivo nuevo llamado favorites.dart. En ese archivo, agrega el siguiente código:

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

Agrega la página Favoritos a lib/screens/favorites.dart

a3c16fc17be25f6c.pngCrea un directorio nuevo llamado screens en el directorio lib y, allí, crea un archivo nuevo llamado favorites.dart. En ese archivo, agrega el siguiente código:

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

Agrega la página principal a lib/screens/home.dart

a3c16fc17be25f6c.pngEn el directorio lib/screens, crea otro archivo nuevo llamado home.dart. En lib/screens/home.dart, agrega el siguiente código:

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

Reemplaza el contenido de lib/main.dart

a3c16fc17be25f6c.pngReemplaza el contenido de lib/main.dart con lo siguiente:

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

La app ahora está completa, pero no probada.

a3c16fc17be25f6c.pngEjecuta la app. Debería verse como en la siguiente captura de pantalla:

b74f843e42a28b0f.png

La app muestra una lista de elementos. Presiona el ícono con forma de corazón en cualquier fila para llenar el corazón y agregar el elemento a la lista de favoritos. El botón Favoritos de AppBar te lleva a una segunda pantalla que contiene la lista de favoritos.

La app ya está lista para las pruebas. Comenzarás a probar la app en el siguiente paso.

5. Prueba de unidades del proveedor

Comenzarás con la prueba de unidades del modelo favorites. ¿Qué es una prueba de unidades? Una prueba de unidades verifica que cada unidad de software individual (sea una función, un objeto o un widget) realice correctamente la tarea deseada.

Todos los archivos de prueba de una app de Flutter, excepto las pruebas de integración, se colocan en el directorio test.

Quita test/widget_test.dart

a3c16fc17be25f6c.pngAntes de comenzar la prueba, borra el archivo widget_test.dart. Agregarás tus propios archivos de prueba.

Cómo crear un nuevo archivo de prueba

Primero, probarás el método add() en el modelo Favorites para verificar que se agregue un nuevo elemento a la lista y que la lista refleje el cambio. Por defecto, la estructura de directorios del directorio test imita la del directorio lib, y los archivos Dart tienen el mismo nombre, pero con _test.

a3c16fc17be25f6c.pngCrea un directorio models en el directorio test. En este directorio nuevo, crea un archivo favorites_test.dart con el siguiente contenido:

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

El marco de trabajo de prueba de Flutter te permite vincular pruebas similares relacionadas entre sí en un grupo. Puede haber varios grupos en un solo archivo de prueba destinado a probar diferentes partes del archivo correspondiente en el directorio lib.

El método test() usa dos parámetros posicionales: el description de la prueba y el callback donde escribes la prueba.

a3c16fc17be25f6c.pngPrueba quitar un elemento de la lista. Inserta la siguiente prueba en el mismo grupo de 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);
});

Ejecuta la prueba

a3c16fc17be25f6c.pngEn la línea de comandos, navega al directorio raíz del proyecto y, luego, ingresa el siguiente comando:

$ flutter test test/models/favorites_test.dart

Si todo funciona, deberías ver un mensaje similar al siguiente:

00:06 +2: All tests passed!

El archivo de prueba completo: test/models/favorites_test.dart.

Si quieres obtener más información sobre la prueba de unidades, consulta Introducción a la prueba de unidades.

6. Prueba de widgets

En este paso, agregarás código para probar los widgets. Las pruebas de widgets son exclusivas de Flutter, y, con ellas, puedes probar cada widget de forma independiente. En este paso, se prueban las pantallas HomePage y FavoritesPage individualmente.

Las pruebas de widgets usan la función testWidget() en lugar de test(). Como la función test(), la función testWidget() admite dos parámetros: description, y callback. Sin embargo, la devolución de llamada admite un elemento WidgetTester como su argumento.

Las pruebas de widgets usan TestFlutterWidgetsBinding, una clase que proporciona los mismos recursos a tus widgets que tendrían en una app en ejecución (p. ej., información sobre el tamaño de la pantalla y la capacidad de programar animaciones), pero sin ejecutarse dentro de una app. En cambio, se usa un entorno virtual para crear una instancia del widget y, luego, probar los resultados. Aquí, pumpWidget inicia el proceso indicando al framework que active y mida un widget en particular como lo haría en una aplicación.

El framework de prueba del widget proporciona detectores para encontrar widgets, por ejemplo, text(), byType() y byIcon().. El framework también brinda comparadores para verificar los resultados.

Comienza por probar el widget HomePage.

Cómo crear un nuevo archivo de prueba

La primera prueba verifica si el desplazamiento de HomePage funciona correctamente.

a3c16fc17be25f6c.pngCrea un archivo nuevo en el directorio test y asígnale el nombre home_test.dart. En el archivo recién creado, agrega el siguiente código:

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 función createHomeScreen() se usa para crear una app que cargue el widget que se probará en una app de Material, en un ChangeNotifierProvider. El widget de Página principal necesita que ambos widgets estén presentes antes en el árbol de widgets para que puedan heredarlos y acceder a los datos que ofrecen. Esta función se pasa como parámetro a la función pumpWidget().

A continuación, prueba si el marco de trabajo puede encontrar un elemento ListView procesado en la pantalla.

a3c16fc17be25f6c.pngAgrega el siguiente fragmento de código 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);
    });
});

Ejecuta la prueba

Primero, ejecuta la prueba de la misma manera en que ejecutarías una prueba de unidades, con el comando siguiente:

$ flutter test test/home_test.dart

La prueba debería ejecutarse rápidamente, y deberías ver un mensaje como el que sigue:

00:02 +2: All tests passed!

También puedes ejecutar pruebas de widgets usando un dispositivo o emulador, lo que te permitirá observar la ejecución de esa prueba. También te brinda la capacidad de usar el reinicio en caliente.

a3c16fc17be25f6c.pngConecta el dispositivo o inicia el emulador. También puedes ejecutar la prueba como una aplicación para computadoras.

a3c16fc17be25f6c.pngDesde la línea de comandos, navega al directorio raíz del proyecto y, luego, ingresa el siguiente comando:

$ flutter run test/home_test.dart

Es posible que tengas que seleccionar el dispositivo en el que quieres ejecutar la prueba. En ese caso, sigue las instrucciones y selecciona 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"):

Si todo funciona, deberías ver un resultado similar al siguiente:

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!

A continuación, realiza los cambios en el archivo de prueba y, luego, presiona Shift + R para reiniciar la app en tiempo real y volver a ejecutar todas las pruebas. No detengas la aplicación.

a3c16fc17be25f6c.pngAgrega más pruebas al grupo que prueba los widgets de la página principal. Copia esta prueba en tu archivo:

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

Esta prueba verifica que, al presionar IconButton, cambie de Icons.favorite_border (un corazón abierto) a Icons.favorite (un corazón relleno) y, luego, vuelva a Icons.favorite_border cuando se presiona nuevamente.

a3c16fc17be25f6c.pngIngresa Shift + R. Esta acción reinicia la app en caliente y vuelve a ejecutar todas las pruebas.

El archivo de prueba completo: test/home_test.dart.

a3c16fc17be25f6c.pngUsa el mismo proceso para probar FavoritesPage con el siguiente código. Sigue los mismos pasos y ejecútalo.

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

Esta prueba verifica si desaparece un elemento cuando se presiona el botón para cerrar (quitar).

Para obtener más información sobre la prueba de widgets, consulta lo siguiente:

7. Prueba la IU de la app con pruebas de integración

Las pruebas de integración se usan para probar en conjunto las piezas individuales de una app. La biblioteca integration_test se usa para realizar pruebas de integración en Flutter. Esta es la versión de Flutter de Selenium WebDriver, Protractor, Espresso o Earl Gray. El paquete usa flutter_driver de manera interna para llevar a cabo la prueba en un dispositivo.

Escribir pruebas de integración en Flutter es similar a escribir pruebas de widgets, excepto que las primeras se ejecutan en un dispositivo móvil, un navegador o una aplicación para computadoras, lo que se conoce como el dispositivo de destino.

Escribe la prueba

a3c16fc17be25f6c.pngCrea un directorio llamado integration_test en el directorio raíz del proyecto, y allí crea un archivo nuevo llamado 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);
      }
    });
  });
}

Ejecuta la prueba

a3c16fc17be25f6c.pngConecta el dispositivo o inicia el emulador. También puedes ejecutar la prueba como una aplicación para computadoras.

a3c16fc17be25f6c.pngEn la línea de comandos, navega al directorio raíz del proyecto y, luego, ingresa el siguiente comando:

$ flutter test integration_test/app_test.dart

Si todo funciona, deberías ver un resultado similar al siguiente:

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. Prueba el rendimiento de la app con Flutter Driver

Escribe una prueba de rendimiento

Crea un nuevo archivo de prueba llamado perf_test.dart en la carpeta integration_test y agrega el siguiente contenido:

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 función ensureInitialized() verifica si se inicializa el controlador de prueba de integración y lo reinicia si es necesario. Establecer framePolicy en fullyLive es útil para probar código animado.

Esta prueba se desplaza por la lista de elementos rápidamente y luego se desplaza hacia arriba. La función traceAction() registra las acciones y genera un resumen del cronograma.

Captura los resultados de rendimiento

Para capturar los resultados, crea una carpeta llamada test_driver con un archivo llamado perf_driver.dart y agrega el siguiente código:

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

Ejecuta la prueba

a3c16fc17be25f6c.pngConecta el dispositivo o inicia el emulador.

a3c16fc17be25f6c.pngEn la línea de comandos, navega al directorio raíz del proyecto y, luego, ingresa el siguiente comando:

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

Si todo funciona, deberías ver un resultado similar al siguiente:

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.

Una vez que la prueba se complete correctamente, el directorio de compilación de la raíz del proyecto contendrá dos archivos:

  1. scrolling_summary.timeline_summary.json contiene el resumen. Para revisar la información que se incluye, abre el archivo con cualquier editor de texto.
  2. scrolling_summary.timeline.json contiene los datos completos del cronograma.

Para obtener más detalles sobre las pruebas de integración, consulta lo siguiente:

9. ¡Felicitaciones!

Completaste el codelab y aprendiste diferentes maneras de probar una app de Flutter.

Qué aprendiste

  • Cómo probar proveedores con la ayuda de pruebas de unidades
  • Cómo probar los widgets con el framework de prueba del widget
  • Cómo probar la IU de la app mediante pruebas de integración
  • Cómo probar el rendimiento de la app mediante pruebas de integración

Si deseas obtener más información para hacer pruebas en Flutter, consulta lo siguiente: