Appium Com Ruby Dividindo o esforço

Thiago Simões
assert(QA)
Published in
9 min readSep 28, 2018

Com o passar do projeto vamos acumulando mais cenário a serem executados em nossas automações com a finalidade de validar que tudo continua funcionando. Conforme essa suíte de testes vai crescendo, o tempo de execução também aumenta e ao invés de ter um feedback rápido para encontrar problemas, você precisa esperar horas as vezes um dia inteiro para que todos os testes sejam executados e caso algum quebre, teremos que reiniciar todo o processo novamente dependendo do problema que for encontrado.

Você pode acabar levando um dia inteiro da Sprint para garantir que as mudanças adicionadas não quebraram o que já funcionava

Uma abordagem válida é criar tags de cenários críticos no qual são os locais que dão mais valor ao produto que estamos construindo. Por outro lado, pode acontecer de um teste que não tenha sido mapeado como crítico, quebrar e o aplicativo acabar saindo do ar e impactar o crash free da aplicacão. Trazendo até mesmo prejuízo para os stakeholders.

Uma outra abordagem é alterar alguns cenários para que sejam cobertos por outros tipos de testes, (um video sobre) isso pode reduzir os testes a serem executados sem perder a qualidade e a confiança em publicar aquela build.

Uma boa prática é sempre revisar o seu código de testes procurando melhorar a forma de localização dos elementos, retirando tempos de espera desnecessários e até mesmo contingências relacionadas ao seu ambiente. Mas as vezes você precisa aceitar que a sua aplicação tem até 30 segundos para responder , só espero que você não esteja trabalhando em uma aplicação médica.

Existem várias formas de melhorar a performance de execução dos nossos testes e uma delas e é o foco deste texto e fazer com que eles possam ser executados de forma paralela.

Já imaginou pegar um device Farm de 100 aparelhos , dividir aqueles seus 100 200 cenários nesses device e ter que esperar 5 minutos para receber o resultado? Maravilhoso não ? Seria se esquecermos de considerar que a média de custo de cada aparelho é de 1200 reais e que 100 desses caras seriam um custo de 120 mil reais , mas podemos usar um device cloud, que custa 0.17 centavos de dólar o minuto do device, levando em consideração o dólar de hoje, o custo do minuto, a quantidade de minutos e a quantidade de testes, 4.10 * 0.17 * 5 * 100 = 348,67 (custo minuto da AWS device Farm) a cada vez que você apertar o play.

Podemos executar em simuladores, não precisamos de 100 devices, apenas o quanto nosso CI/CD aguentar, ou seja, o custo não seria mais do que o projeto já tem. E se, por exemplo, conseguirmos fazer 2 horas e meia de testes virarem 40 minutos já estaremos no lucro.

Com isso podemos pensar em como fazer e que ferramentas utilizar.

Utilizando Appium, Cucumber e Ruby

O projeto já está em Appium, em Ruby e Cucumber logo alterar essas duas estruturas fica um pouco pesado nessa altura do projeto .

Se seguirmos a arquitetura do Cucumber > Ruby > Appium > Device, teremos que quebrar essa solução em algumas etapas.

Mas antes precisamos analisar o que seria um teste paralelo e o que seria uma distribuição de teste.

Enxergo como teste paralelo executar o mesmo teste em devices diferentes com o objetivo de ter uma maior cobertura de sistemas e no caso do artigo dispositivos móveis. como havia dito no começo do texto, às vezes é importante fazer a mesma regressão rodar em dispositivos com versão de sistemas diferentes, ou tamanho de tela diferente para garantir a compatibilidade entre os devices que são utilizados pelo público alvo do aplicativo.

E fazer a distribuição dos testes, seria pegar todos os cenários e dividir em vários pedaços e mandar cada teste ser executado em um device diferente, mas com a mesmas configurações. Pode até ser abordado a ideia de manter vários devices diferente para existir um “equilíbrio” entre dispositivos diferentes.

Dividir os cenários pode parecer uma tarefa simples, mas ao mesmo tempo um pouco traiçoeira.

Uma estratégia válida é fazer o uso de tags, temos 100 cenários e nosso CI/CD atual aguenta 4 emuladores, assim podemos dividir em 4 tags (uma para cada device), e assim conseguir executar um subconjunto de testes em cada device. O problema é que se amanhã tiver que mudar de CI/CD, e este não aguentar os 4 emuladores, você vai precisar refatorar todo o projeto.

Outro ponto é dividir em pastas os arquivos de feature e como o primeiro ficar dependente da estrutura do projeto, mas ainda é mais simples do que abrir cenário por cenário e ficar alterando as Tags.

Por último é encontrar uma ferramenta ou construir alguma que consiga encontrar e separar esses cenários ou features magicamente, para esse serviço encontrei uma GEM chamada “parallel_tests”, existem outras mas a única que funcionou sem exigir coisas adicionais foi ela. Usar é simples.

Só instalar a GEM

“gem install parallel_tests”GitHub:”https://github.com/grosser/parallel_tests"

e no diretório raiz do projeto utilizar o comando

“parallel_cucumber features/ “

features/ é a pasta onde esta os arquivos de features.

Uma característica bacana é que ele olha a pasta de features e a separa em blocos de features, já é uma grande ajuda, mas ainda não é o ideal, porque se você tiver 10 features, mas 4 arquivos de features representarem 70% dos seus cenários você não vai ganhar tanta velocidade assim, já que existem muitos mais cenários em poucos arquivos de feature o que pode desbalancear. Ah e neste momento também não dizemos para a gem em quantas vezes queremos dividir estes cenários, que no caso ele pegara um padrão de 1 Nó por Thread de CPU (referência).

Resolver esses dois casos é fácil.

“parallel_cucumber features/ -n 3 -p — group-by scenarios”

o comando -p — group-by lhe pergunta como você quer agrupar a divisão.

a opção -n lhe pergunta quantos nós você quer utilizar basicamente o quanto você quer dividir.

Mas, e se quisermos executar os testes com uma tag específica? Só adicionar mais um parâmetro.

“parallel_cucumber features/ -n 3 -p — group-by scenarios -o “ — tags @SmokeTests” “

a opção -o aceita algumas opções do cucumber que é o — tags, e sim você precisa passar essas aspas “ — tags @SmokeTests”.

Com isso resolvemos a parte de distribuir o cenários mas agora precisamos integrar ele ao projeto.

No cucumber.yml do projeto devemos adicionar um “perfil” chamado parallel:

Ex: parallel: PLATFORM=android<%= test_batch_id %> -f pretty -f json -o report/report<%= test_batch_id %>.json SCREENSHOT_PATH=screenshots/android/ -r features/support -r features/android -r features/step_definitions — exclude features/ios — tags ‘not @instrumentado and not @WIP and not @MASSA and not @iosparallel: - Nome do Perfil
PLATFORM= - Variável de ambiente utilizada em toda execução
<%= test_batch_id %> - id de execução
-f pretty -f json - Perfumaria e formato do report
-o report/report<%= test_batch_id %> - Local onde é salvo o report
SCREENSHOT_PATH: Local do screenshot
-r *** - Locais que serão lidos para a execução do teste
-exclude locais que serão removidos da execução do teste
-tags - Tags que serão executadas
not - tags que não serão executadas

e também no topo do arquivo um comando para que ele leia esse test_batch_id que é uma variável de ambiente.

Ex:<% test_batch_id = “#{ENV[‘TEST_ENV_NUMBER’]}” %>

Esta variável é importante para conseguir separar as execuções, por exemplo se você quiser três testes simultâneos, o "parallels_test" irá chamar três o perfil parallel, porém o valor da variável de ambiente, "test_batch_id" irá alterar seu valor.

Outro arquivo importante é o env.rb no qual precisa cadastrar as plataformas. Por exemplo: android, android2, android3.

caps = case ENV[‘PLATFORM’]when ‘android’Appium.load_appium_txt file: File.expand_path(‘../support/android/appium.txt’, __dir__), verbose: truewhen ‘android2’Appium.load_appium_txt file: File.expand_path(‘../support/android2/appium.txt’, __dir__), verbose: truewhen ‘android3’Appium.load_appium_txt file: File.expand_path(‘../support/android3/appium.txt’, __dir__), verbose: true

Note que o importante são as “capabilites” diferentes um truque interessante é

caps =Appium.load_appium_txt file: File.expand_path(“../support/paralelo/#{plataforma}/#{appiumtxt}.txt”, __dir__), verbose: true

É possível passar como variável no lugar de ficar passando várias opções no case ou if que normalmente são adicionados neste arquivo.

Chegamos no arquivo de capabilite ou CAPS, neste arquivo você só precisará adicionar dois parâmetros

udid = “emulator-5560”systemPort = 8560

o parâmetro de “systemPort” é um responsável pela comunicação de retorno entre o emulador e o appium server.

o parâmetro “udid” é como o emulador ou device é reconhecido pelo adb, você pode rodar o comando “adb devices” para ver essa lista

O importante é que ela segue um padrão de nome “emulator-” caso o device seja um emulador do AVD, já o número 5560 você pode escolher quando vai iniciar o emulador passando o parâmetro "-port" (numero da porta)

emulator @NomedoEmulador -port 5560, mas lembre que se você fizer um script para subir seus emuladores automaticamente, pule o número de 2 em 2 você pode usar qualquer porta, mas o Google recomenda você escolher entre as portas 5557 até a 5586.

Com isso você já deve ser capaz de fazer seus testes rodarem de forma distribuída.

OK, mas com isso vamos gerar N reports.json e o que fazer com eles depois ?

Para resolver esse problema utilizei uma gem “report_builder”

GitHub: https://github.com/rajatthareja/ReportBuilder

Para instalar

gem install report_builder

e para fazer a junção dos reports

report_builder -s ‘pastaDosJsons’ -o nomeDoReport.html

Note que o tempo fica zoado mas ele não levou todo esse tempo, na verdade foi 1/4 disso. Isso ocorre porque quando você roda quatro testes ao mesmo tempo ele gera quatro artefatos de reporte, o report builder em sua configuração padrão ele considera que esses arquivos foram executados em momentos diferentes, então acaba somando o tempo de todas as features.

Mas e agora como fazer para isso tudo rodar magicamente em um CI/CD ?

Para isso criei um script

GitHub: ‘https://github.com/btfshadow/appiumRubyParallels

No qual automatiza todo esse processo.

Criando os emuladores

while [ $i -lt $NODESN ]; donames[$i]=$name$API$iif [ "$PLATFORM" == "ios" ]; thencreate_ios_emulatorelsecreate_android_emulatorfilet i=$i+1done

Destruindo os emuladores,

while [ $i1 -lt $NODESN ]; doif [ "$PLATFORM" == "ios" ]; thendelete_ios_emulatorelsedelete_android_emulatorfilet i1=$i1+1done

Adicionando variáveis de ambientes.

function environments(){if [ "$PLATFORM" == "ios" ]; thenname=$PLATFORMexport PLATAFORMAAPI=$PLATFORM$APIexport PLATAFORMA=$PLATFORM$APIexport PLATAFORMA_EX='android'export SISTEMA='ios'elseexport PLATAFORMAAPI=$PLATFORM$APIexport PLATAFORMA=$PLATFORM$APIexport PLATAFORMA_EX='ios'export SISTEMA='android'name=$PLATFORMfiif [ "$API" == "10" ]; thenapi=10-0elif [ "$API" == "11" ]; thenapi=11-4elif [ "$API" == "19" ]; thenapi='system-images;android-19;google_apis;x86'aabi=google_apis/x86portemulator="7"elif [ "$API" == "26" ]; thenapi='system-images;android-26;google_apis;x86_64'aabi=google_apis/x86_64portemulator="6"elseecho "Versão de sistema não cadastrada"fi};

Abrindo os emuladores

if [ "$PLATFORM" == "android" ]; thenstart_android_emulatorfi

Gerando o Report

function generate_report(){report_builder -s 'report/' -o together.html}

Com esse texto, conseguimos levantar estratégias de como melhorar a velocidade do feedback de nossos testes, com isso podemos ter mais tempo para trabalhar esses resultados e termos mais controle. Aprendi bastante no processo e espero que quem ler esse texto também aprenda.

Entendo que todo projeto necessita de melhorias, neste momento já considero algumas possibilidades novas que fui aprendendo com a trajetória até este ponto. O levantamento das possibilidades nos permite escolher o melhor caminho a cada situação. Com isso nossas execuções de testes podem ser muito mais rápidas mantendo a confiabilidade, e com um custo baixo já que já possuímos os equipamentos.

Agradecimentos:

Minha Vó a jovem Elizabeth que sempre esteve comigo,

Minha Gerente Samanta Cicília que me ajuda a um ano e meio e que contribui muito com o meu crescimento,

Ao Frederico Moreira ( O Grande Fredão) que desde o primeiro momento sempre vem com desafios bacanas. ( Sinto falta dessa dinâmica),

ao Welington Avelino que sempre me deu dicas e uma força quando necessário,

E aos meus colegas que proporcionaram uma parte da motivação para que eu sempre busque fazer o meu melhor.

--

--