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
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 |
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 |
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
Se não é a primeira vez que esta rodando o build_runner aconselho a dar um clean
flutter pub run build_runner clean
flutter pub run build_runner build
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'
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
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
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", | |
], | |
} | |
] | |
} |
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
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" |
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), | |
); | |
} |
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
Entre em nosso discord para interagir com a comunidade: https://discord.com/invite/flutterbrasil
https://linktr.ee/flutterbrasil
Top comments (0)