Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso Flutter: dominando testes de integração

Flutter: dominando testes de integração

Conhecendo testes de integração - Apresentação

Olá, pessoal! Meu nome é Ricarth Lima e serei a pessoa instrutora em mais um curso no maravilhoso mundo do Flutter.

Audiodescrição: Ricarth é um homem de pele dourada com cabelo crespo. Tem o rosto oval com olhos castanhos escuro e nariz e boca largos. Tem uma barba curta e usa óculos de armação retangular preta. Está com uma camisa de cor azul-marinho com o logo Alura.

Agora que já nos conhecemos, boas-vindas ao curso de testes de integração com Flutter!

Projeto do curso

Para este curso, será utilizado novamente o Listin, conhecido das formações de Flutter. Se você não conhece o Listin, ele é um projeto de gerenciamento de compras, útil para quando você vai fazer compras mensais ou semanais.

Podemos criar uma nova lista contendo os produtos e adicionar novos produtos a ela. Uma das características mais interessantes do listin é que ele mostra o total previsto para a compra quando um produto é marcado como comprado.

No entanto, não criaremos telas nem lógicas para esse aplicativo neste projeto, o que é uma situação bastante comum. É como se uma equipe diferente tivesse entregue o aplicativo já pronto para nós, e agora vamos cobri-lo com testes de integração.

Pré-requisitos

Para fazer isso, precisamos ter uma boa base de Dart (linguagem de programação), Flutter e testes de unidade de widget no Flutter.

Vamos começar o curso para praticar.

Até lá!

Conhecendo testes de integração - Instalando o integration_test

Agora que nos conhecemos, precisamos discutir alguns pontos importantes para tornar o curso o mais agradável possível.

Introdução e Variações de versões das ferramentas

As versões das ferramentas que estamos usando podem variar. Por exemplo, estou usando o PowerShell (terminal de comando), mas poderia ser qualquer terminal aberto, e irei rodar um Flutter para verificar a versão.

flutter --version

Estou usando a versão Flutter 3.19.6 e o Dart 3.3.4 para gravar. Idealmente, você deve ter, pelo menos, essa versão ou uma superior. No entanto, o ideal seria que usássemos a mesma versão tanto de Dart quanto de Flutter para evitar problemas.

Logo em seguida, em uma atividade, será explicado como transformar a sua versão para essa versão específica. Se você estiver com uma versão muito antiga, pode simplesmente rodar um flutter upgrade em qualquer terminal, e isso funcionará. No meu caso, não farei isso agora.

Verificando as instalações do projeto

Depois de resolver a questão da versão do Flutter, vamos analisar o projeto. Fechamos o PowerShell e abrimos o projeto. Teclamos "Ctrl + B" para abrir o explorador do lado esquerdo, clicamos o arquivo pubspec.yaml, porque esse projeto já está funcionalmente pronto.

Muitas coisas já estão instaladas: vários pacotes estéticos, pacotes relacionados ao Firebase e pacotes do curso anterior de teste. Cada um deles tem uma versão indicada ao lado direito de sua linha, que você idealmente deve seguir.

Se precisar, pode pausar o vídeo, mas as versões já estarão definidas no arquivo, dentro do pacote que entregaremos na atividade "Preparando o ambiente".

Outro detalhe muito importante: ao contrário do último curso, onde usamos o Listin para testes, é obrigatória a configuração do Firebase na sua máquina e no seu projeto. Caso contrário, você não conseguirá avançar. Isso significa que há arquivos de configuração específicos do Firebase que você precisa configurar com o seu próprio projeto Firebase. Caso contrário, você estará conectando-se com o meu projeto.

Se eu pressionar "Ctrl + B", ao abrir o arquivo .gitignore, os últimos arquivos listados são ignorados pelo Git. Esses arquivos estão relacionados ao Firebase com a minha configuração. Portanto, preciso que você faça isso na sua configuração também.

.gitignore

// comentário omitido

# Firebase
android/app/google-services.json
ios/firebase_app_id_file.json
ios/Runner/GoogleService-Info.plist
lib/firebase_options.dart
firebase.json

Você pode estar se perguntando se este é um curso de testes de integração ou um curso sobre Firebase. Este não é um curso sobre Firebase, mas é uma questão comum no dia a dia da pessoa desenvolvedora. Frequentemente, você precisará testar aplicações que já utilizam outras dependências.

No caso, o servidor utiliza Firebase. Portanto, é necessário configurar o Firebase para realizar testes mais abrangentes na aplicação.

Leia a atividade "Configurando o Firebase" e siga os passos para configurá-lo no seu projeto.

Agora que tudo está alinhado, começamos a trabalhar. Já discutimos sobre o projeto e suas versões, então configuramos o projeto atual para suportar testes de integração.

Analisando as dependências de desenvolvimento

Para isso, abrimos o VSCode e fechamos o arquivo .gitignore, pois não precisaremos mais dele.

No canto inferior esquerdo, localizamos o arquivo pubspec.yaml e pressionamos "Ctrl + B" para fechar o Explorer. Analisaremos as dependências de desenvolvimento.

pubspec.yaml

# código omitido

# Dependências de desenvolvimento
dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.2
  test: ^1.24.9
  mockito: ^5.4.4
  build_runner: ^2.4.9

# código omitido

É nessa seção que inserimos o pacote de testes de integração.

Adicionando o pacote de testes de integração

Após o dev_dependencies, pressionamos "Enter" e adicionamos integration_test:. Mas não era só colocar aquele flutter pubmed integration test com a tag de dev e rodar? Não exatamente.

Este pacote não é como aqueles que encontramos no pub.dev. Ele se assemelha mais ao flutter_test. Observe nas linhas 30 e 31 que o flutter_test é um pacote que vem incluído quando criamos um projeto Flutter. No caso do integration_test, não é assim.

Qual é a diferença? O integration_test não possui um número de versão específico, como flutter_lints, test ou mockito, porque já está integrado ao SDK do Flutter. Só precisamos habilitá-lo neste projeto.

Habilitando o integration_test

Para fazer isso, na linha 29, teclamos "Enter". Assim como no flutter_test, inserimos sdk: flutter.

# código omitido

# Dependências de desenvolvimento
dev_dependencies:
  integration_test
     sdk: flutter
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.2
  test: ^1.24.9
  mockito: ^5.4.4
  build_runner: ^2.4.9

# código omitido

Note que há uma pequena diferença: não estamos passando um número de versão. A versão da dependência de teste de integração está diretamente relacionada à nossa versão do SDK do Flutter. Por isso, é crucial que tenhamos versões semelhantes ou idênticas.

Ao salvar agora, ele executará o flutter pub get automaticamente. Se tudo estiver correto, nenhum erro ocorrerá e pronto.

Conclusão

Nossa aplicação está preparada para os testes de integração!

Conhecendo testes de integração - Ambiente de teste e primeiro teste

Com tudo configurado, realizaremos o primeiro teste.

Primeiro, é necessário pensar em qual será o teste. Com o emulador aberto, um bom teste inicial para o fluxo de testes de integração é a tela inicial que aparece ao abrir o Listin.

Tela de login de aplicativo em smartphone com a mensagem de boas-vindas "Bem vindo ao Listin!" e instrução "Faça login para criar sua lista de compras." Há campos para inserir e-mail e senha, um link para "Esqueci minha senha.", um botão "Entrar" e um link para cadastro com o texto "Ainda não tem conta? Clique aqui para cadastrar." O design é simples, com predominância de cor verde no fundo e um ícone de carrinho de compras acima do texto de boas-vindas.

Essa tela permite que a pessoa usuária entre com uma conta ou faça o cadastro. Precisamos verificar se o fluxo de mudança de entrar para cadastrar está funcionando corretamente.

Criação da pasta integration_test

Com o emulador aberto, vamos para o VSCode e fechamos o arquivo pubspec.yaml. Fechamos a main.dart também, por enquanto. Pressionamos "Ctrl + B" para abrir o explorador e criamos uma nova pasta para os testes de integração.

Os testes de integração não ficam na pasta test.

O recomendado é ter uma pasta separada para os testes de integração.

Portanto, criamos a nova pasta na raiz do projeto, fora da lib. Selecionamos a raiz do projeto e criamos a nova pasta clicando no segundo ícone de pasta na parte superior esquerda que representa "New folder" ("Nova pasta").

Escrevemos integration_test e teclamos "Enter".

Criação do arquivo app_test.dart

Dentro desta pasta, criamos um arquivo chamado app_test.dart. Para isso, clicamos com o botão direito sobre a pasta e escolhemos a opção "New File", digitamos o nome desejado e teclamos "Enter".

No arquivo app_test podemos fazer os testes de integração.

Construção do arquivo app_test.dart

Primeiro, adicionamos as importações. Na linha 1, escrevemos import e importamos integration_test. Ao digitarmos, será exibido um menu flutuante em que iremos buscar pelo "integration_test/integration_test".

app_test.dart

import "package:integration_test/integration_test.dart";

Logo após, digitamos void main() {}:

import "package:integration_test/integration_test.dart";

void main() {

}

Por enquanto, ele está exibindo um erro. Isso acontece às vezes, mas não há nenhum erro real. Ele diz que está esperando um ponto e vírgula e não encontra integration, mas não é porque escrevemos errado. O problema é que o analisador do Dart (responsável pela verificação) está lento. Isso tem acontecido com frequência, e não sabemos se está relacionado à internet ou à máquina. Se isso acontecer com você, não se preocupe.

Podemos continuar.

Para realizar testes de integração na main(), digitamos "In" e o VSCode sugeriu IntegrationTestWidgetsFlutterBinding. Clicamos nesta opção. Em seguida, colocamos um ponto, onde haverá apenas uma opção: ensureInitialized() ("inicialização segura").

import "package:integration_test/integration_test.dart";

void main() {
    IntegrationTestWidgetsFlutterBinding.ensureInitialized();

}

Para realizar testes de integração, é necessário implementar essas alterações na main.

Logo na sequência, criamos um grupo digitando "group()". No menu flutuante exibido, selecionamos a primeira opção do pacote flutter_test. Assim, teremos a seguinte estrutura: group("description", () {});. Em description digitamos "Fluxo de autenticação".

Dentro das chaves, faremos o primeiro teste.

Criação do primeiro teste

Digitamos "test" e no menu exibido selecionamos a primeira opção "tet(…) -> void". Assim, obtemos a seguinte estrutura: test("description", () => null). Em description, digitamos "Telas de entrar e cadastrar".

# código omitido

void main() {
    IntegrationTestWidgetsFlutterBinding.ensureInitialized();

    group("Fluxo de autenticação", () {
        test("Telas de entrar e cadastrar", () => null);
    });
}

No entanto, há um detalhe muito importante. Nós colocamos test(), mas lidar diretamente com a tela pode exigir mais do que apenas um teste. Testar unidades dentro da integração funcionaria. Mas, como desejamos interagir efetivamente com a tela, vamos mudar para testWidgets(). Nesse caso, é necessário ter um tester e garantir que esse callback seja assíncrono. Alteramos de uma arrow function para uma função normal com chaves "{}".

A única diferença foi na linha 5, que garantimos uma inicialização segura.

# código omitido

void main() {
    IntegrationTestWidgetsFlutterBinding.ensureInitialized();

    group("Fluxo de autenticação", () {
        testWidgets("Telas de entrar e cadastrar", (tester) async {
        
        });
    });
}

Dentro da função, podemos escrever os testes.

Primeiro, subimos o aplicativo com await tester.pumpWidget(MyApp()). Assim, subimos o MyApp() inteiro, dado que estamos simulando toda a aplicação. Na linha seguinte, esperamos o app carregar com await tester.pumpAndSettle().

# código omitido

void main() {
    IntegrationTestWidgetsFlutterBinding.ensureInitialized();

    group("Fluxo de autenticação", () {
        testWidgets("Telas de entrar e cadastrar", (tester) async {
            await tester.pumpWidget(MyApp());
            
            await tester.pumpAndSettle();
        });
    });
}

Estamos criando um pouco mais rápido devido ao fato de que esses são assuntos já tratados no curso anterior.

Logo na sequência, verificamos se ele acha dois TextFormFields com o expect(actual, matcher), que são o de entrar e o de senha. No actual digitamos find.byType() passando TextFormField.

No matcher precisamos encontrar dois widgets usando findsNWidgets() passando o valor 2. Isso para indicar que é preciso buscar por um número "N" de widgets.

# código omitido

    group("Fluxo de autenticação", () {
        testWidgets("Telas de entrar e cadastrar", (tester) async {
            await tester.pumpWidget(MyApp());
            
            await tester.pumpAndSettle();
            
            expect(find.byType(TextFormField), findsNWidgets(2));
        });
    });
}

Com essa instrução, estamos verificando se há dois TextFormField sendo exibidos.

Verificação dos elementos na tela

Na sequência, verificamos se há um botão de entrar visível usando o expect().

Estamos testando nossa tela real para garantir que os elementos necessários estejam presentes.

Dentro do expect() digitamos find.text("Entrar") para buscar por um botão com esse texto. No matcher inserimos findOneWidget.

# código omitido

    group("Fluxo de autenticação", () {
        testWidgets("Telas de entrar e cadastrar", (tester) async {
            await tester.pumpWidget(MyApp());
            
            await tester.pumpAndSettle();
            
            expect(find.byType(TextFormField), findsNWidgets(2));
            
            expect(find.text("Entrar"), findsOneWidget);
        });
    });
}

Ele deve dar aquele clique para mudar de tela. Na tela de entrada, ele passa para a subtela de cadastro. Usaremos await tester.tap() para buscar o botão diretamente pelo texto usando find.text(""). Dentro de text("") passamos Ainda não tem conta?\nClique aqui para cadastrar.. Dessa forma, ele buscará um botão com esse texto e irá clicar.

Ao clicar, precisamos que a tela carregue mais uma vez. Portanto, usamos await tester.pumpAndSettle();.

# código omitido

    group("Fluxo de autenticação", () {
        testWidgets("Telas de entrar e cadastrar", (tester) async {
            await tester.pumpWidget(MyApp());
            
            await tester.pumpAndSettle();
            
            expect(find.byType(TextFormField), findsNWidgets(2));
            
            expect(find.text("Entrar"), findsOneWidget);
            
            await tester
                .tap(find.text("Ainda não tem conta?\nClique aqui para cadastrar."));

            await tester.pumpAndSettle();
        });
    });
}

Por último, verificamos se agora há quatro TextFormFields em vez de dois. Para isso, copiamos a linha 15 com "Ctrl + C". Na linha 24, colamos com "Ctrl + V". Agora, em vez de dois, esperamos ver quatro campos. O botão também deve ter mudado de "entrar" para "cadastrar". Copiamos com "Ctrl + C" e colamos na linha 26. Substituímos "Entrar" por "Cadastrar".

# código omitido

            await tester
                .tap(find.text("Ainda não tem conta?\nClique aqui para cadastrar."));

            await tester.pumpAndSettle();

            expect(find.byType(TextFormField), findsNWidgets(4));

            expect(find.text("Cadastrar"), findsOneWidget);
        });
    });
}

Agora, podemos rodar os testes.

Rodando os testes

No entanto, testes de integração não serão executados no ambiente de teste abstrato como fazíamos nos testes de widget e de unidade. Para esses testes, é necessário um emulador.

O teste ocorrerá no emulador para capturar as especificidades de tela, sistema operacional e hardware via teste de integração.

Já que estamos executando a aplicação no emulador, vamos pausá-la clicando no ícone de quadrado na parte superior direita. Ao abrirmos e verificamos no emulador, observamos que está vazio.

Agora, verificamos se funciona. Por estar demorando, voltamos ao VSCode e teclamos "Ctrl + J" para abrir o terminal e clicamos na aba "DEBUG CONSOLE" na parte superior do terminal.

Abrimos o emulador para verificar se funciona.

Na primeira execução pode ser que demore um pouco.

Notamos no terminal que começou a funcionar, aplica o build, instala e abre o Listin no emulador.

Analisando e corrigindo o erro de execução

O teste começou a rodar e obtemos uma mensagem:

Exception has occurred.

FirebaseException ([core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()) ("Uma exceção ocorreu. Nenhum aplicativo Firebase '[DEFAULT]' foi criado - chame Firebase.initializeApp()).

Interrompemos o teste clicando no canto superior direito.

Fechamos o terminal com "Ctrl + J" e abrimos o explorador com "Ctrl + B". Navegamos para "lib/main", onde temos a aplicação principal e não no teste. Das linhas 11 a 13 inicializamos o Firebase.

main.dart

# código omitido

await Firebase.initializeApp(
  options: DefaultFirebaseOptions.currentPlatform,
);

# código omitido

Precisamos fazer o mesmo no teste de integração. Copiamos com "Ctrl + C". Fechamos a main e voltamos ao app_test. Adicionamos isso como um setup, já que precisa acontecer em todos os testes.

Na linha 9, no grupo, digitamos o "setup" e escolhemos a segunda opção: setUp(() => null). Transformamos a função de seta em uma função normal com chaves ("{}") e chamamos o Firebase. Observe que há um sublinhado em vermelho indicando um erro. Para corrigir, colocamos o setUp() como assíncrono.

app_test

# código omitido

setUp(() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
    );

# código omitido

Além disso, precisamos importar o Firebase. Pressionamos "Ctrl + ponto" sobre Firebase e o VSCode sugere a importação "import library 'package:firebasecore/firebasecore.dart'".

O mesmo vale para o arquivo de configuração DefaultFirebaseOptions. Pressionamos "Ctrl + ponto" na linha 13 para importar e selecionamos a opção "import library 'package:flutterlistin/firebaseoptions.dart'".

Salvamos as alterações.

Agora sim, vamos rodar e ver o que acontece.

Rodando o teste

Mais uma vez, abrimos o "Debug Console" e o emulador. Está parado na tela de listin, devido à inicialização anterior. Mas não tem problema, ele vai fechar e abrir novamente.

Começou a carregar. Fechou e está abrindo o listin novamente. Agora ele deve estar fazendo o setup. Os testes começaram no terminal e abriu a aplicação no emulador, mudou de tela e desapareceu. Ele mudou automaticamente.

Conclusão

Essa é a essência do teste de integração. Não se trata apenas de verificar se as coisas funcionam no ambiente abstrato, mas de executá-las realmente, como se alguém estivesse interagindo. No caso, foi um teste simples para verificar se os TextFormFields e os botões de entrar e cadastrar estavam conforme o esperado.

Vamos continuar praticando?

Sobre o curso Flutter: dominando testes de integração

O curso Flutter: dominando testes de integração possui 152 minutos de vídeos, em um total de 56 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:

Aprenda Flutter acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas