QA | HISTÓRIA

A Evolução dos testes E2E no Zé Delivery

Um pouco mais dos desafios de Qualidade de Software no Zé

Felipe Bellato
ReZÉnha

--

Durante os últimos anos, o Zé passou por um grande crescimento em nossas operações, produto, time, tecnologia… enfim, crescemos em todos os sentidos. Neste artigo, vamos falar sobre a área de Qualidade, contando um pouco como se deu a evolução dos testes (em específico os E2E) desde o princípio, como estamos hoje e os próximos passos.

Primeiramente, o que é um teste E2E?

Um teste E2E, ou End-to-End, é um método de teste utilizado para testar um fluxo da aplicação desde o começo até o fim. Seu intuito é replicar cenários reais feitos pelos usuários com a intenção de validar que o sistema esteja funcionando como o esperado. Um exemplo de cenário de teste aplicado ao Zé Delivery pode ser o fluxo de SignUp. O teste passa por toda a página de cadastro inserindo todos dados necessários e por fim clica em Continuar. Desta maneira um novo usuário é criado e podemos fazer as verificações necessárias para ter certeza que isto foi feito da maneira esperada.

Logo no início, momento em que nosso MVP havia acabado de ser criado, tínhamos testes E2E utilizando a ferramenta Ghost Inspector. Nesta época os testes E2E existentes acabavam servindo mais para falar que eles existiam do que realmente eram úteis na prática, passávamos mais tempo tentando arrumar os testes e o ambiente de desenvolvimento quebrado do que qualquer outra coisa. Sim, é triste falar isso, mas um dia foi a nossa realidade :/

Aos poucos fomos percebendo que a ferramenta era limitada para a complexidade dos testes que gostaríamos de criar, nós tínhamos a necessidade de testar cenários mais específicos e com feedbacks instantâneos devido a velocidade e dinâmica que desenvolvíamos. Foi preciso então estudar outras ferramentas que nos atendessem.

Robot Framework

Como nosso time era enxuto na época e a intenção era que todos de Engenharia pudessem também executar os testes E2E, escolhemos inicialmente o RobotFramework para iniciar as migrações. Era um framework relativamente com baixa curva de aprendizado e que atendia nossas necessidades de complexidade, podendo ser utilizado tanto para testes web, mobile (com SeleniumLibrary e AppiumLibrary), requisições em APIs para criação de massa de dados (RequestsLibrary) e, por fim, asserções em banco de dados (DatabaseLibrary).

O plano inicial foi migrar todos os testes existentes na ferramenta anterior (até então somente web) para a nova ferramenta, centralizando todo o código em um só repositório. Neste momento a execução dos testes era feita nas nossas máquinas quando julgávamos necessário. Só mais tarde foi adicionado no fluxo de CI/CD.

Testes Web

A migração foi concluída com sucesso, conseguimos abandonar a ferramenta antiga e criar testes cada vez mais robustos que atendiam as nossas necessidades. Durante um bom tempo essa solução nos atendeu, mas aos poucos começaram a surgir alguns problemas. Como o código ficava em um repositório apartado, QAs e Desenvolvedores precisavam fazer manualmente o processo de mudar de projeto, apontar para o ambiente sendo desenvolvido e executar os testes na máquina local. Muitas vezes este processo era esquecido e os testes E2E passavam batidos, sem contar que adicionar código de todos contextos diferentes do Zé em um único lugar deixou a manutenção muito mais complexa.

Decidimos então deprecar este repositório e migrar cada um dos testes para seu respectivo repositório de frontend. Assim, ao desenvolver uma funcionalidade nova, com um simples comando era possível executar os cenários de regressão para verificar se nenhum comportamento existente foi afetado. Conseguimos então um feedback muito mais rápido e um processo mais fluido.

A evolução disso foi automatizar ainda mais! Colocamos na pipeline de CI/CD. Optamos pelo seguinte fluxo:

  • Testes unitários e lint, para um feedback mais rápido
  • Build gerando uma Feature Branch, ambiente de teste independente por Pull Request.
  • Execução de todos testes E2E apontando para esta Feature Branch.

Utilizamos o TravisCI, com webdriver sendo executado em headless mode. Assim que os testes são finalizados, todos logs e artefatos gerados durante a execução são enviados para um bucket no s3 e uma operação POST é feita na API do GitHub enviando um link para acesso no Pull Request. Desta maneira tivemos um feedback rápido e integrado sobre todas mudanças sendo desenvolvidas.

Exemplo de um feedback gerado em um PR do GitHub quando algum teste E2E falha.

Testes Mobile

Mais tarde iniciamos a criação dos testes E2E para nossos repositórios de mobile. Continuamos utilizando o RobotFramework, assim pudemos aproveitar boa parte do código e configurações, já que os fluxos eram basicamente os mesmos entre web e app.

E ainda mais do que isso, como nosso app foi criado em Reactive Native, com praticamente só um código de teste, conseguimos executar testes tanto nos apps iOS quanto Android.

Utilizamos a mesma estratégia dos testes web. Inicialmente criamos uma cobertura grande de fluxos, sobretudo para os principais da aplicação, sendo executados inicialmente em máquina local.

Criar, executar e manter testes mobile não é tão simples se comparado aos web. Por natureza são mais complexos e custosos, já que é necessário utilizar um emulador ou device físico. Sendo assim, levou um certo tempo até que atingissem certa maturidade e se tornassem resilientes o suficiente a ponto de serem confiáveis.

Quando atingimos este patamar então, fizemos a integração com CI/CD também. Como a execução dos mesmos nas máquinas do TravisCI não era viável e também tínhamos a necessidade de validar em diversos devices diferentes, foi preciso o auxílio de um Device Farm. Depois de algumas PoCs (proof of concepts) e estudos com diversas ferramentas, optamos então pelo SauceLabs, que oferece uma grande gama de emuladores e integração com o Appium.

Desta maneira, em cada Pull Request aberto pelo time, no fluxo de integração, um ambiente de teste é criado (neste caso em específico, arquivos com extensão .apk ou .ipa), o mesmo artefato é enviado para o SauceLabs e por fim, os testes são executados nos servidores da plataforma apontando para este app gerado.

Vale ressaltar aqui que, como os testes de mobile são muito mais custosos, inicialmente optamos por uma cobertura menor e focada somente nos fluxos principais.

Teste sendo executado no SauceLabs

Escalando o modelo

Beleza, temos testes web e mobile na nossa pipeline nos dando feedbacks rápidos, evitando que bugs subam para produção, mas e agora?
A tendência do número de testes, fluxos e código é sempre aumentar, então o que podemos fazer neste caso? Aperfeiçoar e escalar o modelo!

Começamos a executar os E2E em paralelo. Para isto, utilizamos os próprios comandos de execução do robot em ShellScript:

for suite in $@
do
robot -d results --output ${suite}.xml -s ${suite} tests &
done
***Onde $@ são todas suites de teste a serem executadas em paralelo

Ajustes finos foram feitos até que chegamos a um número ideal máximo de 12 suites executadas em paralelo nos repositórios de web. No caso dos testes em mobile, sempre ficamos limitados ao número de máquinas concorrentes disponíveis em nosso plano do SauceLabs, então tivemos que viver com isto e eventualmente aumentar o plano de acordo com a necessidade.

Paralelizar os testes nos trouxe muito mais agilidade e segurança, mas isto também criou outra complexidade para nosso ciclo de desenvolvimento: alguns testes, ao serem executados de maneira concorrente, fatalmente impactavam no resultado de outros.

Mesmo utilizando o [Setup] e [Teardown] das suites para deixar a massa de dados necessária pronta em cada teste e depois retornar ao estado anterior, o problema se tornava cada vez mais evidente com a escala de nosso time.

Mais pessoas no time de Engenharia significam mais PRs abertos, e consequentemente mais testes sendo executados simultaneamente.

Um exemplo simples e real levando em conta dois cenários diferentes:

i. Fluxo de compra completo.

ii. Validação de feedback para o usuário em uma área não coberta por distribuidores.

O teste i. precisa ter um distribuidor aberto para que o produto seja adicionado na sacola e o usuário siga o fluxo até o fim, finalizando a compra.
Já para o teste ii., o distribuidor precisa estar fechado para que a mensagem correta seja exibida ao usuário.
Quando executados ao mesmo tempo, o teste i pode pegar o distribuidor fechado ou o teste ii pode pegar o distribuidor aberto. Mesmo que por poucos segundos, isto já é suficiente para gerar falsos negativos e deixar os testes flaky.

Neste caso, implementamos duas melhoras:

  • Novas Tentativas: Ao fim da execução dos testes, todos que apresentam falhas são executados novamente para ter certeza se ele está realmente quebrado, ou se foi um falso negativo. Segue aqui um artigo que explica um pouco melhor a vantagem de utilizar novas tentativas e seu impacto na taxa de sucesso dos testes.
robot -d results --rerunfailed results/${suite}.xml -s ${suite} tests &
  • Isolamento de testes: Testes que de alguma maneira podem ter impacto em outros foram isolados em uma só suite.
    No início da execução, toda massa de dados necessária é criada de maneira randômica. Os testes são executados em série e por fim, toda massa é deletada para não poluir a base. As outras suites são executadas em paralelo a ela, eliminando o risco de falha por concorrência.

Zé QA Library

Melhoramos a execução e conseguimos replicar o modelo para outros repositórios. Cada repositório começou a crescer muito e a complexidade individual de cada um também começou a aumentar.

Por um lado, o ecossistema todo do Zé Delivery está ligado em todas pontas, mas por outro, nem todas as pessoas do time tem visibilidade de como todas regras de negócio funcionam em todos diferentes contextos. Nós tínhamos muito código repetido espalhado em todos repositórios e por este motivo a manutenção se tornava muito custosa.

Por exemplo: quando era preciso alterar alguma regra de negócio no nosso core, teríamos que fazer esta alteração em todos repositórios ao mesmo tempo e sincronizar o merge para ter certeza que nenhum time fosse impactado negativamente.

Foi diante deste problema que surgiu a Zé QA Library. Uma Library do RobotFramework customizada internamente para nossa necessidade. Isto possibilitou centralizar todos os códigos repetidos organizados por keywords. Segue aqui um artigo que explica como criar libraries customizadas utilizando python.

E o melhor, é simples acessar todas keywords criadas na lib em todos projetos, basta somente:

  1. Adicionar uma linha no arquivo requirements.txt de cada projeto com a versão desejada.
  2. Adicionar um import na suite desejada.
Trecho de código importando a library interna do Zé (ZeLibrary)

Ah, e como fizemos para criá-la?Juntos! Literalmente toda ela foi desenvolvida de maneira conjunta pelas pessoas de QAs do Capítulo de Qualidade em DOJOs online. Isto também estimulou a troca de conhecimento tanto sobre regras de negócio do Zé quanto conhecimento técnico para que todos pudessem utilizar e evoluir a lib conforme a necessidade.

Próximos passos

Como sempre aqui no Zé, estamos procurando maneiras de evoluir e nos preparar para o que está por vir: muito crescimento e escalabilidade. No momento, estamos aplicando padrões de projeto consistentes e unificados em todos repositórios para garantir a manutenção mais simples e eficiente aplicando uma Guideline de RobotFramework desenvolvida internamente para nossas necessidades.

Estamos também estudando novas ferramentas e frameworks que atendam a necessidade individual de cada projeto e/ou serviço que virão pela frente :)

Espero que tenha gostado do artigo. Se você se interessou pelo desafio, corre lá na nossa página de Carreiras, tem vagas abertas no time de Tecnologia :)

--

--