Limitações que precisam ser violadas ou como aceleramos os testes funcionais três vezes

imagem

Testes funcionais são uma coisa útil. No início, eles não demoram muito tempo, mas o projeto está crescendo e são necessários mais e mais testes. Não pretendemos tolerar uma desaceleração na velocidade de entrega e, reunindo forças, aceleramos os testes funcionais três vezes. No artigo, você encontrará dicas universais, no entanto, notará um efeito especial em grandes projetos.

Brevemente sobre a aplicação


Minha equipe está desenvolvendo uma API pública que fornece dados para usuários 2GIS. Ao acessar o 2gis.ru e procurar por "Supermercados", você obtém uma lista de organizações - esses são os dados da nossa API. Em nosso RPS de mais de 2000, quase todos os problemas se tornam críticos se algum tipo de funcionalidade quebrar.

O aplicativo é escrito em Scala, os testes são escritos em PHP, o banco de dados é o PostgreSQL-9.4. Temos cerca de 25.000 testes funcionais, eles levam 30 minutos para serem concluídos em uma máquina virtual dedicada para regressão geral. A duração dos testes não nos incomodou muito - estamos acostumados ao fato de que os testes podem durar 60 minutos na estrutura antiga.

Como aceleramos os chamados testes "rápidos"


Tudo começou por acidente. Como geralmente acontece. Apoiamos um recurso após o outro, passando nos testes ao mesmo tempo. O número deles aumentou e o tempo necessário para completá-lo também. Uma vez que os testes começaram a se arrastar para fora dos prazos alocados a eles, e, portanto, o processo de execução foi encerrado à força. Testes incompletos estão repletos de problemas perdidos no código.

Analisamos a velocidade dos testes e a tarefa de acelerá-los acentuadamente se tornou relevante. Então começou um estudo chamado "Os testes funcionam devagar - conserte".

Abaixo estão três dos grandes problemas que encontramos nos testes.

Problema 1: jsQuery mal utilizado



Todos os dados que temos são armazenados no banco de dados PostgreSQL. Principalmente na forma de json, então usamos ativamente o jsQuery.

Aqui está um exemplo de uma consulta que fizemos no banco de dados para obter os dados necessários:

SELECT * FROM firm WHERE json_data @@ 'rubrics.@# > 0' AND json_data @@ 'address_name = *' AND json_data @@ 'contact_groups.#.contacts.#.type = “website”' ORDER BY RANDOM() LIMIT 1 

É fácil perceber que o exemplo usa json_data várias vezes seguidas, embora seja correto escrever isso:

 SELECT * FROM firm WHERE json_data @@ 'rubrics.@# > 0 AND address_name = * AND contact_groups.#.contacts.#.type = “website”' ORDER BY RANDOM() LIMIT 1 

Tais deficiências não foram muito evidentes, pois nos testes não escrevemos todas as consultas com as mãos e, em vez disso, usamos o QueryBuilders, que eles mesmos compõem após especificar as funções necessárias. Não achamos que isso poderia afetar a velocidade de execução da consulta. Assim, no código, é algo como isto:

 $qb = $this>createQueryBulder() ->selectAllBranchFields() ->fromBranchPartition() ->hasRubric() ->hasAddressName() ->hasWebsite() ->orderByRandom() ->setMaxResults(1); 

Não repita nossos erros : se houver várias condições em um campo JSONB, descreva-as todas na estrutura do operador único '@@'. Após refazer, aceleramos o tempo de execução de cada solicitação duas vezes. Anteriormente, a solicitação descrita levava 7500ms, mas agora leva 3500ms.

Problema 2: Dados de Teste Extra



O acesso à nossa API é fornecido por chave, cada usuário tem sua própria API. Anteriormente, nos testes, muitas vezes era necessário modificar as configurações principais. Por causa disso, os testes caíram.

Decidimos criar várias chaves com as configurações necessárias para cada execução de regressão para evitar problemas de interseção. E como a criação de uma nova chave não afeta a funcionalidade de todo o aplicativo, essa abordagem nos testes não afeta nada. Eles viveram nessas condições por cerca de um ano, até começarem a lidar com a produtividade.

Não há muitas chaves - 1000 peças. Para acelerar o aplicativo, os armazenamos na memória e atualizamos a cada poucos minutos ou sob demanda. Assim, depois de salvar a próxima chave, os testes iniciaram o processo de sincronização, cujo final não esperávamos - recebemos a resposta "504", que foi gravada nos logs. Ao mesmo tempo, o aplicativo não indicava nenhum problema e achamos que tudo funcionou muito bem para nós. O processo de teste de regressão continuou. E no final, descobrimos que sempre tivemos sorte e nossas chaves foram salvas.

Vivemos na ignorância até checar os logs. Descobrimos que criamos as chaves, mas não as excluímos após a execução dos testes. Assim, acumulamos 500.000 deles.

Não repita nossos erros: se você modificar o banco de dados de alguma forma nos testes, certifique-se de que o banco de dados retorne ao seu estado original. Depois de limparmos o banco de dados, o processo de atualização de chaves acelerou 500 vezes.

Problema 3: Amostragem Aleatória



Gostamos de testar o aplicativo em dados diferentes. Temos muitos dados e problemas são encontrados periodicamente. Por exemplo, houve um caso em que não recebemos dados sobre publicidade, mas os testes detectaram esse problema a tempo. É por isso que em cada solicitação de nossos testes você pode ver ORDER BY RANDOM ()

Quando analisamos os resultados das consultas, com e sem aleatoriedade, com EXPLAIN, vimos um aumento de desempenho de 20 vezes. Se falarmos sobre o exemplo acima, sem randomização, ele funciona por 160ms. Nós pensamos seriamente sobre o que fazer, porque realmente não queríamos abandonar completamente a casa aleatória.

Por exemplo, em Novosibirsk, existem cerca de 150 mil empresas e, quando tentamos encontrar uma empresa com endereço, site e cabeçalho, recebemos um registro aleatório de quase todo o banco de dados. Decidimos reduzir a seleção para as 100 primeiras empresas que se encaixam em nossas condições. O resultado dos pensamentos foi um compromisso entre uma seleção constante de dados e velocidade diferentes:

 SELECT * FROM (SELECT * FROM firm_1 WHERE json_data @@ 'rubrics.@# > 0 AND address_name = * AND contact_groups.#.contacts.#.type = "website"' LIMIT 100) random_hack ORDER BY RANDOM() LIMIT 1; 

Dessa maneira simples, não perdemos quase nada na aceleração 20x. O tempo de execução de uma solicitação é de 180ms.

Não repita nossos erros: esse momento, é claro, dificilmente pode ser chamado de erro. Se você realmente tem muitos testes, pense sempre em quanto você precisa de aleatoriedade nos dados. A troca entre a velocidade de execução da consulta no banco de dados e a exclusividade da seleção nos ajudou a acelerar as consultas SQL em 20 vezes.

Mais uma vez, uma pequena lista de ações:


  1. Se especificarmos várias condições para selecionar dados no campo JSONB, eles deverão ser listados em um único operador '@@'.
  2. Se criarmos dados de teste, exclua-os. Mesmo que pareça que a presença deles não afete a funcionalidade do aplicativo.
  3. Se você precisar de dados aleatórios para cada execução, encontramos um compromisso entre a exclusividade da amostra e a velocidade de execução.

Aceleramos a regressão três vezes graças a modificações simples (e para algumas, provavelmente até óbvias). Agora nossos testes de 25K passam em 10 minutos. E esse não é o limite - estamos otimizando o código a seguir. Não se sabe quantas descobertas inesperadas nos aguardam lá.

Source: https://habr.com/ru/post/pt419671/


All Articles