Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso Flutter: Estratégias de Teste e Depuração

Flutter: Estratégias de Teste e Depuração

Conceitos e ferramentas de teste - Apresentacao

Apresentando a instrutora e o tema do curso

Olá! Meu nome é Priscila Brambila.

Audiodescrição: Priscila é uma mulher branca, com cabelo ruivo, usa óculos e veste uma camiseta preta.

Hoje, vamos aprender a realizar testes unitários de widget (ferramenta) e de integração.

Introduzindo os conceitos de testes unitários e de integração

No curso de hoje, vamos compreender melhor o que são cada um desses testes. A partir da aplicação vista no curso anterior de arquitetura, veremos como construir todos eles de forma modularizada e organizada, começando pelos mais simples e isolados até chegar aos testes de integração, que cobrem toda a nossa aplicação. Se desejamos aprender a lidar com testes e realizá-los da melhor forma possível, vamos seguir juntos para entender melhor como isso funciona. [♪]

Conceitos e ferramentas de teste - Conceitos e ferramentas de testes

Discutindo a importância dos testes unitários

Hoje vamos discutir sobre testes unitários e as ferramentas de desenvolvimento disponíveis no Flutter. Ao concluir o desenvolvimento de um aplicativo, é comum acreditar que todas as funcionalidades foram testadas e estão funcionando corretamente. No entanto, pode haver incertezas sobre se todos os cenários foram realmente testados. Mesmo afirmando que o aplicativo está 100% funcional, pode haver dúvidas internas sobre a integridade de todas as funcionalidades.

Com o tempo, é possível que bugs apareçam ou que alterações em funcionalidades que estavam funcionando perfeitamente acabem afetando outras partes do aplicativo. Durante a manutenção do código ou ao adicionar novas funcionalidades, pode-se perceber que, embora algo tenha funcionado inicialmente, não mantém o funcionamento perfeito o tempo todo. É nesse contexto que os testes se tornam essenciais.

Explicando a função dos testes na aplicação

Os testes na aplicação criam cenários necessários para cada funcionalidade, permitindo que elas sejam testadas individualmente e em conjunto. Isso garante que, ao realizar manutenção ou adicionar novas funcionalidades, nada será comprometido. Além disso, os testes são úteis para a documentação, pois mostram os casos de uso da aplicação. Assim, eles servem como uma forma de documentar o que foi feito, facilitando o trabalho de outras pessoas desenvolvedoras que venham a trabalhar no projeto.

Explorando os tipos de testes no Flutter

No Flutter, existem diferentes tipos de testes. Começamos com os testes unitários, que geralmente testam regras de negócio de forma isolada, como funções clássicas e lógicas específicas. Por exemplo, se há uma função de soma na aplicação, testar essa função isoladamente é um teste unitário, pois está focado apenas nessa função, sem envolver telas, widgets ou qualquer elemento visual.

Para realizar testes unitários no Flutter, utilizamos o pacote test. Este pacote é voltado para testes unitários dentro do Flutter, conforme descrito na documentação oficial.

Abordando os testes de widgets

Falando em aspectos visuais, vamos agora para os testes de widgets, que testam a interface do usuário (UI) de forma isolada, sem executar o aplicativo. O que isso significa? Ele testa desde um botão de forma isolada, para verificar se o clique aciona a função específica associada a ele, até mesmo se uma tela com um formulário está preenchendo todos os campos corretamente e, por exemplo, habilitando o botão de submissão no final. Isso caracteriza um teste de widget.

Para os testes de widgets, utilizamos o flutter_test, que também é uma biblioteca específica do Flutter.

Explicando a execução dos testes de widgets

Por que especificamente sem executar o aplicativo? Porque, na verdade, ele não testa todas as funcionalidades integradas, como chamadas de API e outras interações externas. Ali, apenas a parte onde um widget interage com outro e transmite os dados ou interações necessárias é testada, sem a interação com classes ou elementos externos.

Introduzindo os testes de integração

Agora, vamos entrar no terceiro tipo de teste, que é o teste de integração. O teste de integração simula o uso geral do aplicativo, com cliques, navegação entre telas e tudo mais. Nesse caso, não é necessário simular uma funcionalidade substituta para testar, como fazemos no teste de widget. Aqui, realizamos o teste real do aplicativo, com cliques, navegação, chamadas de API e outros elementos. É um tipo de teste específico no Flutter e, para quem vem de outras stacks, seria equivalente a um teste de ponta a ponta (end-to-end).

Utilizando a ferramenta de teste de integração

Para os testes de integração, utilizamos o integration_test, que é utilizado para testes de integração ou end-to-end, caso você esteja mais familiarizado com esse termo em outras stacks. Essa ferramenta facilita muito o desenvolvimento desses testes.

Concluindo a introdução às ferramentas de teste

Agora que já conhecemos um pouco sobre essas ferramentas de teste e os tipos de testes, vamos ver na prática como isso funciona.

Conceitos e ferramentas de teste - Recapitulando projeto Fokus

Revisando o projeto anterior

Para entender melhor como os testes funcionam, vamos revisitar nosso projeto desenvolvido no curso anterior, relacionado à arquitetura. Vamos passar rapidamente por ele para compreender seu funcionamento, como algumas partes do código operavam e tudo mais, para que fiquemos contextualizados com o que será feito após nossos testes.

Considerando nosso projeto Focus, ele possuía basicamente três modos: o modo Focus, pausa curta e pausa longa. Na prática, os três realizavam a mesma função, mas com tempos diferentes. O modo Focus inicializa o temporizador para 25 minutos, oferecendo a opção de parar completamente ou pausar. Se clicarmos em pausar e depois continuar, ele retoma de onde parou. Caso optemos por parar, ele mostra o tempo em que foi interrompido, mas, ao inicializá-lo novamente, ele reinicia do zero.

Explorando o código do projeto

Os outros dois modos funcionam exatamente da mesma forma, mas com tempos diferentes associados a cada tipo de pausa.

Agora, passando para o nosso código, vamos começar pelos enums, onde temos os tipos de timers. Como mencionamos anteriormente, eles funcionam, em geral, da mesma forma, mudando basicamente o nome, o tempo associado a eles e a imagem que está associada a cada tela. Cada tela possui sua própria imagem: o modo Foco, a pausa curta e a pausa longa.

Definindo o enum TimerType

Para representar esses modos, utilizamos um enum chamado TimerType. Vamos ver como ele é definido:

enum TimerType {
  focus('Modo Foco', 25, 'assets/focus.png'),
  shortBreak('Pausa Curta', 5, 'assets/pause.png'),
  longBreak('Pausa Longa', 15, 'assets/long.png');

  const TimerType(this.title, this.minutes, this.imageName);
  final String title;
  final int minutes;
  final String imageName;
}

Configurando a classe AppConfig

Considerando isso, temos também a nossa classe AppConfig, que contém todas as constantes utilizadas no nosso aplicativo, permitindo que sejam acessadas de forma mais fácil, todas de um mesmo lugar. Vamos dar uma olhada na definição dessa classe:

import 'package:flutter/material.dart';

class AppConfig {
  // Tempos em minutos
  static const int focusTime = 25;
  static const int shortBreakTime = 5;
  static const int longBreakTime = 15;

  // Cores do tema
  static const Color backgroundColor = Color(0xFF021123); // Background padrão
  static const Color buttonColor = Color(0xFFB072FF); // Cor dos botões

  // Títulos
  static const String appTitle = 'Fokus';
  static const String focusTitle = 'Modo Foco';
  static const String shortBreakTitle = 'Pausa Curta';
  static const String longBreakTitle = 'Pausa Longa';

  // Imagens
  static const String logoImage = 'assets/logo.png';
  static const String homeImage = 'assets/home.png';
  static const String focusImage = 'assets/focus.png';
  static const String pauseImage = 'assets/pause.png';
}

Implementando o TimerViewModel

Outra funcionalidade importante é o nosso ViewModel do timer, que é responsável por gerenciar a interação do usuário com a tela. Quando clicamos para inicializar o timer, a tela precisa responder indicando que o timer está rodando, ou, quando paramos ou pausamos, que ele parou. Vamos ver como isso é implementado:

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

class TimerViewModel extends ChangeNotifier {
  bool isPlaying = false;
  Timer? _timer;
  Duration duration = Duration.zero;

  void startTimer(int initialMinutes, ValueNotifier<bool> isPaused) {
    duration = Duration.zero;
    isPlaying = true;
    notifyListeners();
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (isPaused.value) return;

      if (duration.inMinutes < initialMinutes) {
        duration = Duration(seconds: duration.inSeconds + 1);
        notifyListeners();
      } else {
        stopTime();
      }
    });
  }

  void stopTime() {
    _timer?.cancel();
    isPlaying = false;
    notifyListeners();
  }
}

Estruturando os widgets do timer

Depois disso, temos os widgets. O nosso widget do timer é o bloco que contém os botões de iniciar, continuar, pausar ou parar. A nossa tela, chamada TimerPage, inclui também a imagem correspondente. Aqui está um exemplo de como os botões de controle são implementados:

// Botões de controle
SizedBox(
  width: double.infinity,
  height: 50,
  child: ListenableBuilder(
    listenable: timerViewModel,
    builder: (context, child) {
      bool isPlaying = timerViewModel.isPlaying;
      return ElevatedButton(
        onPressed: () {
          if (isPlaying) {
            timerViewModel.stopTime();
          } else {
            timerViewModel.startTimer(
              widget.initialMinutes,
              isPausedNotifier,
            );
          }
          isPausedNotifier.value = false;
        },
      );
    },
  ),
)

Estruturando a HomePage

Além disso, temos a HomePage, que contém os três botões, a imagem inicial e toda a tela inicial. Vamos ver como a HomePage é estruturada:

import 'package:flutter/material.dart';
import 'package:fokus/shared/enums/timer_type.dart';
import 'package:fokus/shared/utils/app_config.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppConfig.backgroundColor,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Image.asset(AppConfig.logoImage, height: 80),
              const SizedBox(height: 24),
              Expanded(child: Image.asset(AppConfig.homeImage)),
              const SizedBox(height: 40),
              Column(
                // Aqui você pode adicionar os botões e outros widgets
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Introduzindo as camadas de teste

Com essa aplicação, vamos entender hoje como funcionam as três camadas de teste: os unitários, os de widget e os de integração.

Sobre o curso Flutter: Estratégias de Teste e Depuração

O curso Flutter: Estratégias de Teste e Depuração possui 156 minutos de vídeos, em um total de 32 atividades. Gostou? Conheça nossos outros cursos de Flutter em Mobile, ou leia nossos artigos de Mobile.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Escolha a duração do seu plano

Conheça os Planos para Empresas