DEV Community

Cover image for FLUTTER_GHERKIN criando automações de teste de uma forma mais simples
Toshi Ossada for flutterbrasil

Posted on

1

FLUTTER_GHERKIN criando automações de teste de uma forma mais simples

O tema “testes automatizados” vem ganhando grande notoriedade pois desta maneira as empresas conseguem entregar mais qualidade em seus projetos a um custo não muito alto.

No Flutter temos diversos tipos de facilidades de criarmos testes (já falamos de alguns tipos aqui) e cada vez mais vão surgindo ferramentas que vem facilitando a escrita de testes, mas convenhamos que por mais simples que seja escrever um teste de widget ou de integração ainda precisamos ter um conhecimento técnico dentro do Flutter e dentro do DART.

Entretanto a comunidade Flutter viu este problema e criou um pacote que irá ajudar muito uma pessoa sem conhecimento nenhum no Flutter a criar cenários de testes utilizando o GHERKIN, estou falando do flutter_gherkin(https://pub.dev/packages/flutter_gherkin/).

Mas afinal o que é GHERKIN?

O GHERKIN é um técnica de padronização de escrita de especificações de cenários de testes baseada em seu negocio.

O objetivo de sua utilização e deixar os testes automatizados de fácil leitura e compreensão para qualquer pessoa, inclusive pessoas totalmente leigas no assunto. O GHERKIN deve seguir alguns padrões seguindo a regra de negocio da aplicação e deve ser escrito em forma de “passos”(“steps”) para especificar cada etapa e interação que o usuário irá ter com o sistema.

Imagine o cenário que iremos testar uma calculadora e iremos testar de 3+ 38 esta apresentando o resultado 41.

Pré condição: Abrir a calculador  
Pressionar o botão de 3  
Pressionar o botão de +  
Pressionar o botão de 3  
Pressionar o botão de 8  
Pressionar o botão de =  
Deve mostrar o resultado 41
Enter fullscreen mode Exit fullscreen mode

Esperar o que apareça o resultado 41

Dado este cenário agora só precisamos converter para o padrão do GHERKIN, ele possui algumas palavras-chave que talvez você já tenha ouvido, os famosos (GIVEN-WHEN-THEN).

Given (pt: Dado): Utilizado para especificar uma pré condição que deverá ser executado antes do cenário de teste. Por se tratar de uma pré condição, normalmente vem escrito no passado;
When (pt: Quando): Utiliza-se o When quando alguma ação é executada no cenário de teste e isso irá gerar uma reação que será testada no Then.
Then (pt: Então): É a etapa de validação do cenário, basicamente iremos validar as reações geradas pelas ações do When
And (pt: E): Caso seja necessário mais uma interação com o sistema para complementar um fluxo.
But (pt: Mas): No geral serve a mesma funcionalidade do “And”, porém é normalmente utilizado após uma validação negativa depois do “Then”

Sendo assim nosso cenário reescrito ficaria

Dado que o usuário abriu a calculadora

Quando o usuário pressionar o botão 3

E o usuário pressionar o botão +

E o usuário pressionar o botão 3

E o usuário pressionar o botão 8

E o usuário pressionar o botão =

Então o resultado deverá aparecer 41

Muito legal esse conceito se quiser saber mais sobre GHERKIN recomendo a leitura https://blog.onedaytesting.com.br/gherkin/

Só aplicando essa técnica e escrevendo cenários desta forma para o critério de aceite de uma feature já ajudaria bastante a vida e ajudaria ainda mais saber que poderemos pegar essa escrita de cenário e automatizar utilizando o flutter_gherkin.

flutter_gherkin(https://pub.dev/packages/flutter_gherkin/)

Hoje (05/06/2022) o flutter_gherkin está na versão estável 2.0.0, NÃO RECOMENDO usar esta versão, ao invés disso utilize a versão em pré release mais atual pois ela já roda em cima da nova API de integration_test do Flutter

Além do flutter_gherkin precisaremos ter mais 2 dependências em nosso projeto, o integration_test que poderá ser pego diretamente do sdk do flutter e o build_runner. Podem deixa-los como dev_dependencies

dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
flutter_gherkin: ^3.0.0-rc.17
build_runner: ^2.1.10
view raw pubspec.yaml hosted with ❤ by GitHub



A primeira coisa que precisamos fazer no diretório test_driver arquivo que será nosso ponto de entrada dos testes integration_test_driver.dart


import 'package:integration_test/integration_test_driver.dart'
as integration_test_driver;
Future<void> main() {
return integration_test_driver.integrationDriver(
timeout: const Duration(minutes: 90),
);
}

Também será necessário criar o arquivo build.yaml na raiz do projeto para configuração do build_runner para gerar os testes
targets:
$default:
sources:
- lib/**
- pubspec.*
- $package$
# Allows the code generator to target files outside of the lib folder
- integration_test/**.dart
view raw build.yaml hosted with ❤ by GitHub

Crie o diretório integration_test na raiz do projeto que agora iremos começar a trabalhar em cima dele.

Agora podemos começar a escrever nossos cenários de testes, eu geralmente se, criaremos por exemplo o teste da calculadora, devera conter a extensão .feature


@tag
Feature: Calculator
@debug
Scenario: 3 + 38 = 41
Given I open calculator
And calculator is empty
When I press number 3
And I press operation '+'
And I press number 3
And I press number 8
And I press operation '='
Then I expect result '41'

Agora precisamos criar o arquivo de configuração dos testes de integração para isto crie o arquivo gherkin_suite_test.dart dentro do diretório integration_test.


import 'package:flutter_gherkin/flutter_gherkin.dart'; // notice new import name
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin/gherkin.dart';
// The application under test.
import 'package:intergration_test/main.dart' as app;
part 'gherkin_suite_test.g.dart';
@GherkinTestSuite()
void main() {
executeTestSuite(
configuration: FlutterTestConfiguration(
stepDefinitions: [],
order: ExecutionOrder.alphabetical,
features: [RegExp('features/*.*.feature')],
reporters: [
StdoutReporter(MessageLevel.error)
..setWriteLineFn(print)
..setWriteFn(print),
ProgressReporter()
..setWriteLineFn(print)
..setWriteFn(print),
TestRunSummaryReporter()
..setWriteLineFn(print)
..setWriteFn(print),
JsonReporter(
writeReport: (_, __) => Future<void>.value(),
)
],
),
appMainFunction: (World world) async => app.main(),
);
}

note que ele tem um atributo @GherkinTestSuite() isso indica que essa classe terá um código que será gerado através do build_runner, então não se preocupe com os erros.

Para gerar o arquivo .g.dart pelo build_runner execute

flutter pub run build_runner build
Enter fullscreen mode Exit fullscreen mode

Se não é a primeira vez que esta rodando o build_runner aconselho a dar um clean

flutter pub run build_runner clean
Enter fullscreen mode Exit fullscreen mode
flutter pub run build_runner build
Enter fullscreen mode Exit fullscreen mode

Agora precisamos programar os passos, temos os seguintes passos

Abrir calculadora -> I open calculator  
Pressionar um numero -> I press number 3  
Pressionar operaçao -> I press operation '+'  
Pressionar igual -> I press operation '='  
Verificar resultado -> I expect result '41'
Enter fullscreen mode Exit fullscreen mode

Na pasta steps criamos nossos arquivos de steps

Given

Primeiro vamos fazer o given criamos uma função que retorna um StepDefinitionGeneric e utilizando a função given() passo a expressão do passo e no corpo dela o que será feito, como se fosse um teste de widget. Podemos usar os finders e as actions (enterText, Tap, etc).

StepDefinitionGeneric givenIOpenCalculator() {
return given<FlutterWorld>(
'I open calculator',
(context) async {
final finder =
context.world.appDriver.findBy('btnCalculate', FindType.key);
await context.world.appDriver.tap(finder);
},
);
}



Se precisarmos passar algum parâmetro utilizamos as variações do given… given1, given2, e…

return given1<String, FlutterWorld>(
'I type {string}',
(value, context) async {
final finder = context.world.appDriver.findBy('txtName', FindType.key);
await context.world.appDriver.enterText(
finder,
value,
);
},
);
}

WHEN

Para construir os steps de When é bem simular, utilizo a função when de acordo com a quantidade de parâmetros que tenho no passo.

StepDefinitionGeneric whenIPressNumber() {
return when1<int, FlutterWidgetTesterWorld>(
'I press number {int}',
(number, context) async {
final tester = context.world.rawAppDriver;
await tester.pumpAndSettle();
final finder = context.world.appDriver.findBy('btn$number', FindType.key);
await tester.tap(finder);
await tester.pump();
},
);
}
StepDefinitionGeneric whenIPressOperation() {
return when1<String, FlutterWidgetTesterWorld>(
'I press operation {string}',
(operation, context) async {
final tester = context.world.rawAppDriver;
await tester.pumpAndSettle();
final finder = context.world.appDriver.findBy(operation, FindType.text);
await tester.tap(finder);
await tester.pump();
},
);
}

Consigo recuperar o widget por diversos paramentos, como string, tipo, chave.

Também consigo recuperar os descendentes ou acedentes

E diversas outras ações assim como nos testes de widgets, consulte a documentação.

Then

No then é a mesma coisa, utilizo a função then() conforme a quantidade de parâmetros e tenho disponível todos os métodos de um teste de integração, neste passo geralmente é onde irei testar os expect()

StepDefinitionGeneric thenIExpectResult() {
return then1<String, FlutterWorld>(
'I expect result {string}',
(text, context) async {
await context.world.appDriver.waitForAppToSettle();
final finder = context.world.appDriver.findBy(
text,
FindType.text,
);
context.expect(finder, findsOneWidget);
},
);
}

Agora lá no arquivo de configuração do GHERKIN preciso adicionar essas definições de passos (gherkin_suite_test.dart)

Execute novamente o build_runner

flutter pub run build_runner clean
flutter pub run build_runner build
Enter fullscreen mode Exit fullscreen mode

E agora para ver os testes executar bastar executar

flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart
Enter fullscreen mode Exit fullscreen mode

E o resultado será

Para conseguir debuggar no Visual Studio CODE colocando breaking points e tudo mais basta adicionar o launch.json

{
"version": "0.2.0",
"configurations": [
{
"name": "Debug integration_test",
"program": "test_driver/integration_test_driver.dart",
"request": "launch",
"type": "dart",
"args": [
"--target=integration_test/gherkin_suite_test.dart",
],
}
]
}
view raw launch.json hosted with ❤ by GitHub



Desta forma com todos os passos programados qualquer um pode chegar e montar vários cenários de testes

Também consigo criar ganchos(hooks) para ter funções que sejam executadas antes/depois de cada cenário e antes/depois de cada passo, basta criar uma classe estendendo de Hook e sobrescrever os métodos.

class ResetAppHook extends Hook {
@override
int get priority => 100;
/// Resets the application state before the test is run to ensure no test side effects
@override
Future<void> onAfterScenarioWorldCreated(
World world,
String scenario,
Iterable<Tag> tags,
) async {
}
}

Dai no arquivo de suit só adicionar a propriedade hooks.

Traduções

Se o inglês é um problema não se preocupe, o flutter_gherkin tem traduções para a maioria das linguagens e o português é uma delas, você pode conferir em https://github.com/cucumber/common/blob/main/gherkin/gherkin-languages.json

Basta trocar as palavras chaves do GHERKIN para este dicionário

"pt": {
"and": [
"* ",
"E "
],
"background": [
"Contexto",
"Cenário de Fundo",
"Cenario de Fundo",
"Fundo"
],
"but": [
"* ",
"Mas "
],
"examples": [
"Exemplos",
"Cenários",
"Cenarios"
],
"feature": [
"Funcionalidade",
"Característica",
"Caracteristica"
],
"given": [
"* ",
"Dado ",
"Dada ",
"Dados ",
"Dadas "
],
"name": "Portuguese",
"native": "português",
"rule": [
"Regra"
],
"scenario": [
"Exemplo",
"Cenário",
"Cenario"
],
"scenarioOutline": [
"Esquema do Cenário",
"Esquema do Cenario",
"Delineação do Cenário",
"Delineacao do Cenario"
],
"then": [
"* ",
"Então ",
"Entao "
],
"when": [
"* ",
"Quando "
]
}

No seu arquivo de freature basta adicionar no começo de cada funcionalidade # language: codigo do pais, no caso do português utilize pt.
# language: pt
Enter fullscreen mode Exit fullscreen mode

Nossa funcionalidade ficará da seguinte forma

# language: pt
Funcionalidade: Home
Cenário: Usuário escreve nome na tela
Dado que escrevo "Toshi Ossada"
Quando paro de escrever
Então Espero o texto "Olá Toshi Ossada"
view raw 1_home.feature hosted with ❤ by GitHub



Agora basta configurar as Steps e rodar o teste e voilá tem seu gherkin em português.

StepDefinitionGeneric givenITypeText() {
return given1<String, FlutterWorld>(
'que escrevo {string}',
(value, context) async {
final finder = context.world.appDriver.findBy('txtName', FindType.key);
await context.world.appDriver.enterText(
finder,
value,
);
},
);
}
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
StepDefinitionGeneric whenIStopType() {
return when<FlutterWidgetTesterWorld>(
'paro de escrever',
(context) async {
final tester = context.world.rawAppDriver;
await tester.pump();
},
);
}
import 'package:flutter/material.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin/gherkin.dart';
StepDefinitionGeneric thenIExpectTheText() {
return then1<String, FlutterWorld>(
'Espero o texto {string}',
(text, context) async {
await context.world.appDriver.waitForAppToSettle();
// get the parent list
final finder = context.world.appDriver.findBy(
text,
FindType.text,
);
var label = await context.world.appDriver.widget<Text>(finder);
context.expect(label.data, text);
context.expect(finder, findsOneWidget);
},
configuration: StepDefinitionConfiguration()
..timeout = const Duration(seconds: 5),
);
}
view raw steps.dart hosted with ❤ by GitHub



Legal ne? É impressionante como vem surgindo ferramentas para que facilitam cada dia mais nossa vida, principalmente se tratando de testes.

Segue o projeto de exemplo

https://github.com/toshiossada/flutterIntegrationTest

Image description

Entre em nosso discord para interagir com a comunidade: https://discord.com/invite/flutterbrasil
https://linktr.ee/flutterbrasil

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️