Flutter

Testando nossa aplicação

Rafael Almeida Barbosa

Android/Flutter Developer - Cofundador Lovepeople

I

Cofundador FlutterSP

Porque testar nossa aplicação ?

  • Validar o funcionamento do que foi desenvolvido;
  • Garantir o funcionamento do que foi desenvolvido;
  • Reduzir ao máximo o número de bugs;

1 - 35

2 - 35

Proporção de testes

2 - 35

Tipos de testes disponíveis no Flutter

  • Unitários
  • Widgets / Snapshot
  • Integração 

3 - 35

Testes Unitários

Um teste de unidade testa uma única função, método ou classe. O objetivo de um teste de unidade é verificar a exatidão de uma unidade de lógica sob uma variedade de condições.

3 - 35

Testes Widgets

Um teste de widget (em outras estruturas de interface do usuário conhecidas como teste de componente) testa um único widget. O objetivo de um teste de widget é verificar se a interface do usuário do widget parece e interage conforme o esperado. Testar um widget envolve várias classes e requer um ambiente de teste que forneça o contexto apropriado do ciclo de vida do widget.

3 - 35

Testes de integração

Testes de unidade e testes de widget são úteis para testar classes, funções ou widgets individuais. No entanto, eles geralmente não testam como peças individuais funcionam juntas como um todo ou capturam o desempenho de um aplicativo executado em um dispositivo real. Essas tarefas são executadas com testes de integração.

4 - 35

Criando testes unitários

dev_dependencies:
  test: <latest_version>
  • Adicionando dependencia

4 - 35

Criando testes unitários

counter_app/
  lib/
    counter.dart
  test/
    counter_test.dart
  • Criando arquivo de teste

4 - 35

Criando testes unitários

class Counter {
  int value = 0;

  void increment() => value++;

  void decrement() => value--;
}
  • Criando classe que será testada

4 - 35

Criando testes unitários

// Import the test package and Counter class
import 'package:test/test.dart';
import 'package:counter_app/counter.dart';

void main() {
  test('Counter value should be incremented', () {
    final counter = Counter();

    counter.increment();

    expect(counter.value, 1);
  });
}
  • Criando o teste

4 - 35

Criando testes unitários


flutter test test/counter_test.dart
  • Rodando o teste

4 - 35

Criando testes unitários

Vamos fazer juntos

4 - 35

Criando testes de widgets


dev_dependencies:
  flutter_test:
    sdk: flutter
  • Adicionando dependência

4 - 35

Criando testes de widgets

class MyWidget extends StatelessWidget {
  const MyWidget({
    Key? key,
    required this.title,
    required this.message,
  }) : super(key: key);

  final String title;
  final String message;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Center(
          child: Text(message),
        ),
      ),
    );
  }
}
  • Criando widget a ser testado

4 - 35

Criando testes de widgets

void main() {
  // Define a test. The TestWidgets function also provides a WidgetTester
  // to work with. The WidgetTester allows you to build and interact
  // with widgets in the test environment.
  testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
    // Test code goes here.
  });
}
  • Criando arquivo de teste

4 - 35

Criando testes de widgets

void main() {
  testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
    // Create the widget by telling the tester to build it.
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
  });
}
  • Construindo o widget utilizando WidgetTester

4 - 35

Criando testes de widgets

  • Anotações sobre o 'pump'

Após o pumpWidget o 'WidgetTester' disponibiliza outras maneiras de fazer um rebuild do widget:

  1. tester.pump(Duration duration)
  2. tester.pumpAndSettle() - (Ideal para esperar animações )

4 - 35

Criando testes de widgets

  • Buscando widgets utilizando 'Finder'
void main() {
  testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));

    // Create the Finders.
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');
  });
}

4 - 35

Criando testes de widgets

  • Verificando Widget usando `Matcher`
void main() {
  testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');

    // Use the `findsOneWidget` matcher provided by flutter_test to verify
    // that the Text widgets appear exactly once in the widget tree.
    expect(titleFinder, findsOneWidget);
    expect(messageFinder, findsOneWidget);
  });
}

4 - 35

Criando testes de widgets - SNAPSHOT

expectLater(
  find.byType(MaterialApp),
  matchesGoldenFile('./golden_files/minha_tela.png'),
);

4 - 35

Criando testes de widgets - SNAPSHOT

  • Adicionando ao nosso teste de widget
void main() {
  testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');

    // Use the `findsOneWidget` matcher provided by flutter_test to verify
    // that the Text widgets appear exactly once in the widget tree.
    expect(titleFinder, findsOneWidget);
    expect(messageFinder, findsOneWidget);
    
    expectLater(
      find.byType(MyWidget),
      matchesGoldenFile('./golden_files/my_widget.png'),
    );
  });
}

4 - 35

Criando testes de widgets - SNAPSHOT

  • Criando imagem modelo para teste

flutter test --update-goldens {path-file-teste}

Pronto ele irá criar uma imagem que servirá como modelo de verificação na pasta especificada ('./golden_files/minha_tela.png').

Sempre que rodar o teste ele vai verificar o que foi desenhado com  a foto modelo.

4 - 35

Criando testes de widgets

Vamos fazer juntos

4 - 35

E se precisarmos mockar alguma coisa?

4 - 35

Mocktail

4 - 35

Mocktail

4 - 35

Criando testes de integração


dev_dependencies:
  integration_test:
    sdk: flutter
  flutter_test:
    sdk: flutter

  • Adicionando dependência

4 - 35

Criando testes de integração


lib/
  ...
integration_test/
  foo_test.dart
  bar_test.dart
test/
  # Other unit tests go here.
  • Estrutura de pastas

4 - 35

Criando testes de integração

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized(); // NEW
  
  binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; // NEW

  testWidgets('should load screen', (tester) async {
    /// Escreve o teste similar ao teste de widget
  });
}
  • Criando arquivo de teste

4 - 35

Criando testes de integração

flutter test integration_test/foo_test.dart -d <DEVICE_ID>
  • Executando teste
flutter test integration_test

4 - 35

Criando testes de integração

Vamos fazer juntos

4 - 35

Criando testes de integração


pushd android
# flutter build generates files in android/ for building the app
flutter build apk
./gradlew app:assembleAndroidTest
./gradlew app:assembleDebug -Ptarget=integration_test/<name>_test.dart
popd
  • Subindo no Firebase Test Lab

4 - 35

Criando testes de integração


<flutter_project_directory>/build/app/outputs/apk/debug/<file>.apk
<flutter_project_directory>/build/app/outputs/apk/androidTest/debug/<file>.apk
  • Subindo no Firebase Test Lab

4 - 35

Criando testes de integração

  • Subindo no Firebase Test Lab

4 - 35

Criando testes de integração

  • Realizando teste de performance 

await binding.traceAction(
  () async {
    // Scroll until the item to be found appears.
    await tester.scrollUntilVisible(
      itemFinder,
      500.0,
      scrollable: listFinder,
    );
  },
  reportKey: 'scrolling_timeline',
);

4 - 35

Criando testes de integração

  • Realizando teste de performance 
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_timeline']);

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

        // Then, write the entire timeline to disk in a json format.
        // This file can be opened in the Chrome browser's tracing tools
        // found by navigating to chrome://tracing.
        // Optionally, save the summary to disk by setting includeSummary
        // to true
        await summary.writeTimelineToFile(
          'scrolling_timeline',
          pretty: true,
          includeSummary: true,
        );
      }
    },
  );
}

Criar um arquivo chamado  `perf_driver.dart` (custom `test_driver`)

4 - 35

Criando testes de integração

  • Realizando teste de performance 
flutter drive \
  --driver=test_driver/perf_driver.dart \
  --target=integration_test/scrolling_test.dart \
  --profile

4 - 35

Criando testes de integração

  • Realizando teste de performance 
scrolling_summary.timeline_summary.json

Nesse arquivo contem o sumário da performance. Os dados macro

scrolling_timeline.timeline.json

Nesse arquivo contem toda a timeline e pode ser aberto em chrome://tracing para ser analisado

Referencias

  • Testing Flutter apps. Disponível em: <https://docs.flutter.dev/testing#widget-tests> Acesso em: 6 de março de 2022

 

Obrigado!

GithubPage: http://rafaelbarbosatec.github.io

LInkedln: https://www.linkedin.com/in/rafael-almeida-7667a063

Medium: @rafaelbarbosatec

Testando nossa aplicação

By Rafael Almeida Barbosa

Testando nossa aplicação

  • 412