Artilharia pesada na sua API com Artillery

Estresse sua API até ela querer te bater

Matheus Galdino
Accenture Digital Product Dev

--

Nos dias de hoje, nossa rotina se baseia cada vez mais em aplicativos. Temos aqueles que pedem táxi, fazem delivery alimentar e não alimentar, ajudam na comunicação, permitem ver seu e-mail, baixam outros aplicativos e por aí vai. Porém, o que alguns sabem (ou não) é que por trás de uma aplicação bonita e bacana sempre tem um back-end que realiza operações de negócio e que precisa ser muito bem testado para garantir a confiabilidade do produto.

Esses mergulhadores sorrindo atrás das máscaras às 3 da manhã, depois de uma ligação surpresa, pedindo pra corrigir um falha no sistema. É MUITO verdade esse bilhete.

Para garantir essa confiabilidade, grandes empresas que produzem produtos digitais fazem, dentre vários testes, um que é chamado teste de stress e, com base nos resultados colhidos, conseguem gerar pontos de ação e direcionamentos estratégicos para o produto.

No meu caso, nós (eu e o time) tínhamos uma feature implementada que dependia de um serviço externo, e queríamos saber o quanto esse serviço aguentava de porrada até começar a apresentar lentidão e finalmente mandar erros de timeout. Para fazer esses testes, eu usei a ferramenta Artillery, principalmente por conta da facilidade que ela me forneceu. Mas não precisa se preocupar com isso agora, que eu vou entrar em detalhe daqui a pouco 😜.

Sobre testes de desempenho, carga e stress

O teste tem um papel fundamental na garantia da qualidade de um produto. As empresas estão cada vez mais preocupadas com os testes que são realizados em seus projetos. Com isso, as exigências quanto aos tipos e técnicas de testes utilizados têm ganhado cada vez mais espaço, tornando fundamental a definição de uma boa estratégia, que deve ser definida de acordo com a necessidade individual de cada projeto.

Para isso, existem alguns tipos de testes que podem ser usados e podem até parecer semelhantes, mas possuem atuações diferentes:

  • Teste de desempenho: busca extrair informações sobre o desempenho do sistema em cenários normais de uso. Por exemplo, um teste usando carga de 1.000 usuários para um sistema que aguenta até 1.000 usuários simultâneos.
  • Teste de carga: busca extrair informações sobre o desempenho do sistema em cenários um pouco diferentes do normal, a fim de entender o comportamento. Por exemplo, quantas transações serão suportadas por minuto quando aumentarmos os usuários simultâneos para 4.000 ou 5.000 em um sistema que aguenta 2.000 usuários.
  • Teste de stress: São testes de performance que visam testar o nosso software sob condições extremas de uso em um curto período de tempo. Por exemplo, quantas transações por minuto solicitadas por 6.000, 50.000, 100.000 usuários simultâneos serão suportadas pela aplicação sob condições não especificadas de hardware e software.
Você consegue diferenciar um teste de carga para um de stress quando ver o número de requisições simultâneas e reagir dessa maneira

Testes de stress (ou Load tests) são aqueles que buscam colocar um software sob condições bastante extremas durante um pequeno espaço de tempo. Em geral, nesse tipo de teste, são simuladas grandes picos de usuários de maneira a investigar o quanto o software aguenta.

Sobre o teste de carga feito

Quando eu planejei meus testes de stress, eu precisava de uma ferramenta que ensinasse a mim (que nunca tinha feito um desse tipo) e ao meu time como fazer o teste de stress. Foi aí que, pesquisando, achei a biblioteca Artillery, vista por aí como a lib de Startups, dada a sua facilidade. A lib em si é feita em JavaScript, e traz o propósito de oferecer escalabilidade para a execução dos testes, além de fazer com que qualquer um do time consiga entender, uma vez que o script do teste é feito usando YAML ou JSON.

Antes de executar, vamos analisar o script. De cara, podemos categorizar em duas partes: a parte de configuração e a de cenários. A parte de configuração possui alguns pontos importantes e necessários, como o destino das requisições, fases do teste (por meio das fases, tentamos simular momentos da nossa aplicação), certificados, variáveis externas ao teste para apoio (opcionais) e funções de apoio (definidas como processor, também opcionais).

Configurações de um arquivo arbitrário, usado no Artillery

Além disso, temos a definição dos cenários do teste, que são montados como fluxos. O cenário começa a ser montado usando um método HTTP, além do caminho do recurso a ser testado. Junto a isso, podemos definir uma variável em tempo de execução para capturar a estrutura de retorno recebida, que pode ser com o uso de JSONPath, XMLPath ou Regex, e reutilizar em outro cenário desse mesmo fluxo, algo que faz bastante sentido.

Cenários de um arquivo arbitrário a ser usado no artillery

Entretanto, como podemos ver na segunda imagem, existem alguns operadores definidos, como afterResponse, beforeRequest, e o ifTrue. Enquanto eu estava estudando como montar esse teste, me deparei com a necessidade de gerar um arquivo de log conforme as requisições eram feitas, de alterar possíveis corpos de requisição ou simplesmente bloquear cenários inválidos (na ordem dos meus desesperos no momento). Como esse tipo de situação é bastante comum, podemos contar com um arquivo JavaScript chamado processor, importado nas configurações, que contém esse conjunto de funções exportadas.

Se você achar que é bobeira, tenta fazer o log sem usar esse carinha acima hehe. Se quiser uma base melhor, dá uma olhada aqui

Agora que já conhecemos o suficiente de um script, chegou a parte mais legal que é botar pra funcionar. Para isso, é necessário ter uma versão do node instalada (eu usei a 8.9.0) e instalar a lib Artillery, usando o comando npm install -G artillery. Ao término da instalação, podemos executar nossa aplicação usando o comando artillery run <nome_do_script>.yml -e development -o reports/cupons_report.json. Se olharmos esse comando, é possível entender dois atributos:

  • O environment ou -e no script, permite que seja usada uma das opções definidas na configuração;
  • O output ou -o no script, permite capturar e gerar um relatório, em formato JSON.

O resultado dessa execução, como representado abaixo, é um conjunto de dados parciais que juntos geram um resultado que pode ser usado para decisões.

Em caso de algo faltando, a documentação da ferramenta pode nos ajudar a solucionar 👀

Esse conjunto de dados parciais representa, cada um deles, um intervalo de 10 segundos, contendo um grupo de informações estatísticas:

  • Cenários criados — número de usuários virtualmente criados para fazer a simulação;
  • Cenários completos — número de usuários virtuais que completaram as simulações dentro da faixa de 10 segundos;
  • Requisições completas- número de requisições (nesse caso HTTP) que foram enviadas e recebidas;
  • RPS ou Requests per Second- a média de requisições completas por segundo dentro de um grupo de 10 segundos;
  • Latência da requisição — tendo milissegundos como unidade de medida, representa o tempo que a operação levou para iniciar e terminar. As propriedades p95 e p99 representam a latência de maneira percentual, ou seja, um p99 igual a 500ms indica que 99 de 100 requisições levaram 500ms para completar;
  • Codes — representa o status das respostas HTTP recebidas pelos usuários virtuais. Em caso de Nan, significa que algo ocorreu no processo;
  • Errors — erros que foram registrados durante a execução do script, como por exemplo ETIMEDOUT.

Pra gerar o relatório HTML do conteúdo acima, basta executar o comando artillery report <caminho usado como output>.html o que resulta o arquivo com nome coupons_report.json.html, que concentra as informações totais capturadas e exibidas pela lib.

Aquele bizu no relatório que vai nos dizer se tá ou não aprovada a feature

Eu considero esse relatório algo de imenso valor, uma vez que essas informações, ao meu ver, representam a base para tomadas de decisões estratégicas e de segurança, guiadas por meio de dados quantitativos que nos dizem quanta porrada e tapa o software em questão aguenta.

Mesmo permitindo fazer testes de diferentes ferramentas sem muita complicação, ainda tem alguns pontos a melhorar. Um exemplo é o fato de usar apenas uma thread para execução dos scripts, o que, dependendo do cenário a ser coberto, pode ser um pouco limitante (embora esteja no release plan da versão 2.0). Além disso, alguns recursos para tentar simular, de maneira mais próxima do individual, o comportamento de um usuário virtual dentro dos cenários.

Como desenvolvedores, cada vez mais precisamos nos preocupar com a performance das nossas APIs. Não importa o quão bom o nosso front-end foi arquitetado, se nossas APIs demorarem para responder isso vai refletir para o usuário final. Esse tipo de teste pode nos ajudar a escolher determinada tecnologia ou linguagem de programação na hora do desenvolvimento das nossas APIs e em futuras tomadas de decisão para garantir o melhor produto.

Grato pela atenção! Ficou alguma dúvida ou tem algo para colaborar? Deixe um comentário! Até a próxima. =)

--

--