Desenvolvimento

29 out, 2015

QA, CI, Flaky tests e confiabilidade para devs

Publicidade

Temos cada vez mais buscado na Concrete Solutions implementar CI (Continuous Integration) e CD (Continuous Delivery). Esse tem sido um dos desafios que tenho encontrado no projeto mais recente em que trabalhei, uma vez que esse conceito está sendo adotado aos poucos nos projetos de apps mobile no Brasil.

Já conseguimos automatizar os testes funcionais para os nossos aplicativos desenvolvidos, e isso é ótimo. Podemos percorrer incessantemente todos os fluxos de telas dos nossos aplicativos facilmente em busca de erros. Se quisermos, também podemos realizar os testes em múltiplos aparelhos. Dois parâmetros subjetivos podem variar na hora de especificarmos nossos testes BDD:

  1. O rigor dos testes: quanto mais rigoroso for o teste, mais custoso será a implementação e, consequentemente, a sua manutenção com a evolução da aplicação. O rigor pode variar desde a verificação se estamos nas telas certas ao clicarmos nos botões dos nossos apps até se todos os itens de uma tela – fotos, dados a serem exibidos etc. – realmente estão aparecendo;
  2. O papel dos testes funcionais no processo de desenvolvimento.

É esse segundo tópico que eu vou abordar, passando por como eu tenho direcionado os esforços e me baseando no que tenho visto na web vindo de engenheiros de QA seniores.

É possível citar dois cenários possíveis de utilização dos testes automatizados.

No primeiro caso, os testes têm papel secundário: eles são realizados periodicamente e servem como ferramenta para que o engenheiro de QA detecte problemas na aplicação e alerte os desenvolvedores.

No segundo caso, os testes automatizados têm papel primário no fluxo de integração contínua e de entrega. Dessa forma, podemos, por exemplo, alterar um fluxo de CI de:

Build + Testes Unitários (DEVs) -> Deploy

para

Build + Testes Unitários (DEVs) -> Testes Funcionais (QA) -> Deploy

Com esse novo fluxo, os aplicativos só serão distribuídos para os stakeholders, ou só serão postos em produção quando eles passarem pelo crivo dos testes funcionais.

Esse é um desafio e tanto.

Para chegarmos a essa meta de incluir os testes funcionais nos fluxos de CI e CD, nós, de QA, devemos levar em consideração dois problemas na implementação de testes: os erros randômicos e como lidar com eles.

Erros randômicos

Por que se preocupar?

Muito comuns em testes automatizados, os testes com esse comportamento são conhecidos como flaky tests. Esses erros, que ocorrem aleatoriamente, não necessariamente são falhas da aplicação. Explico: muitas vezes programamos os testes para que eles realizem as tarefas que solicitamos. Testamos em nossas máquinas locais e tudo parece estar certo. Porém, como parte do processo de CI e CD, queremos que uma máquina dedicada e gerida por servidor, fora do nosso escopo de utilização, realize os testes para o seu time. Nessas situações, duas coisas precisam ser levadas em consideração:

  • O teste será realizado em vários dispositivos e simuladores, nos quais, a princípio, não tínhamos previsto o comportamento sob uma bateria de testes automatizados.
  • As variáveis de ambientes, bem como outras configurações de plataforma de desenvolvimento, tais como do Android Studio e XCode, podem estar configuradas de maneira diferente da máquina na qual você tenha desenvolvido os seus testes.

Por causa disso, quando os mesmos testes são realizados em máquinas de uso para integração contínua, é muito comum que elas apontem erros falsos. Quando repetimos os testes que apontaram aqueles erros, eles acabam passando: foi um falso alarme.

Vamos analisar esta situação: você quer que seus testes façam parte do workflow de integração contínua ou de entrega contínua de maneira eficiente. Para isso, seus testes precisam ser os mais determinísticos possíveis, ou seja, que tenham um único – e confiável – resultado esperado para cada cenário de testes e só acusem erros que realmente sejam da aplicação. É nesse segundo item que os erros randômicos são prejudiciais.

Pense por um segundo: os desenvolvedores fazem alterações em seus códigos e muitas vezes não apresentam erros (detectáveis pela bateria de testes automatizados). Porém, os seus testes automatizados acusam erros o tempo todo. O resultado será que seus testes não serão confiáveis, que as partes interessadas (devs e stakeholders) não darão muita atenção aos erros apontados pelos testes e que o fluxo de integração, ou de entrega contínua, ficará quase sempre interrompido, ou seja, não haverá integração ou entrega quando deveria, porque seus testes não permitem que o fluxo continue.

Soluções

Encontrei três abordagens e vou falar um pouco de cada uma delas.

Remover cenários de testes

Esta solução é a mais simples de todas: remova os testes que apresentarem erros randômicos.

No Cucumber, você pode associar tags para cada cenário. Daí é possível executar os testes e solicitar que os cenários marcados com as tags que você determinar não sejam executados.

Vantagem:

  • Permite CI e CD de volta ao normal o mais rápido possível: você pode configurar o servidor de testes para nunca executar os testes marcados. Daí você pode investigar mais tranquilamente as causas dos erros, bem como melhorar a programação dos seus testes de forma a evitar que esses erros aleatórios continuem ocorrendo.

Desvantagem:

  • Demora para solucionar: talvez você acumule tantos casos de erros de flaky tests que acabará nunca solucionando todos, e eles nunca voltarão para a sua pilha de execução.

Usar plugins de “retestes”

Um dos plugins que eu considerei inserir no servidor de testes do nosso projeto de desenvolvimento mais recente foi o flaky tests handler plugin. Trata-se de uma ferramenta para manter rastros dos casos de testes que eventualmente apontem erros randômicos. Com ele, é possível exibir diversas formas de estatísticas e retestar os erros para averiguar se ele foi randômico ou não. A ferramenta é compatível com Git e Maven.

Vantagens:

  • Gerência em nível de servidor de testes: você não precisa alterar drasticamente o script dos seus testes para “retestar” aqueles que apontaram erros e averiguar se é um flaky test.
  • Exibe estatísticas.
  • Permite executar um número predeterminado de vezes os testes que julgar necessário.

Desvantagem:

  • Restrição de uso: ele foi desenvolvido somente para projetos que utilizem Maven e Git.

Usar recurso de “retestes” nativo do Cucumber

Outra possibilidade interessante que descobri é a de usar um recurso nativo do Cucumber para testar de novo automaticamente os cenários que apresentarem erros. Por meio de uma sintaxe simples, você pode solicitar, com um único comando, que o Cucumber  execute duas vezes os testes. O segredo está nos parâmetros passados: solicitaremos que os testes que não passaram sejam registrados em arquivo txt, e que esse arquivo seja o parâmetro de entrada para a segunda bateria de testes.

Veja um exemplo simples desse comando, que retirei deste blog:

cucumber features/retest.feature -f pretty -f rerun --out rerun.txt || cucumber @rerun.txt

O “||’ serve somente para executar o lado direito do comando caso o do lado esquerdo retorne false – quando um dos testes não passa. O “rerun –out rerun.txt” serve para persistir a lista de testes que não passou em um formato que o Cucumber compreenda. O “@rerun.txt” serve como parâmetro informando que somente os testes especificados em rerun.txt devem ser executados.

Vantagens:

  • Suporte nativo: não há dependências de recursos externos.
  • Simples.

Desvantagens:

  • Sem gerência: não fornece muita informação gerencial sobre flaky tests. Você precisa configurar para buscar isso por conta própria.

Conclusão

Buscando tornar os nossos testes automatizados em mobile cada vez mais presentes nos processos de integração e entrega contínuos, é imprescindível que os testes sejam confiáveis, ou seja, que eles só detectem os erros realmente pertinentes aos aplicativos.

Existem diversas formas de solucionar os problemas relacionados aos flaky tests – casos de testes que apontam erros inexistentes nos aplicativos. Neste artigo, abordamos três soluções. Eu, particularmente, aderi ao uso do recurso de “rerun” do Cucumber, pois é simples de usar e já é nativo: pode ser utilizado com o Calabash.

A desvantagem, conforme citamos, é que precisamos nos preocupar em gerar nosso próprio gerencial de erros: como mostrar aqueles que foram erros da aplicação e aqueles que foram “falsos alarmes”. Na Concrete, optamos por considerar que os testes passaram, mesmo em casos de falso alarme. Porém, mostramos os erros apontados, pois, mesmo que aparentemente não sejam erros da aplicação, podem ser problemas ainda não detectados no aplicativo. No entanto, essas considerações podem variar de acordo com o projeto. Não há regra específica.

E você, tem alguma sugestão de como tornar seus testes funcionais mais confiáveis para entrega e integração contínuas? Mande seus comentários e ajude a comunidade de QA gerando mais conhecimento. =)

Fontes de inspiração para este texto:

Até a próxima!