
Boa tarde, queridos leitores. Meu nome é Victor Burov. Trabalho como desenvolvedor no ISPsystem e quero compartilhar minha experiência em automação de testes.
Aconteceu que o teste manual prevaleceu em nosso país e os testadores passaram muito tempo realizando as mesmas ações. Uma vez pensamos: por que não ensinar o painel a repetir as ações do testador, porque, de fato, todos eles se transformam em chamadas de API específicas. Isso permitiria que as pessoas escrevessem testes mesmo sem habilidades de programação.
Decidimos escrever um módulo para criar testes automáticos. Para que o testador pudesse simplesmente pressionar o botão para criar um teste, atender às condições do caso de teste, no final, clique em "Concluir" - e pronto, o teste estava pronto! Uma ideia simples, mas perceber que não era fácil. Porque queríamos que este módulo fosse adaptado ao máximo para nossos produtos e aproveitasse uma interface unificada: para que o vídeo gravado parecesse um caso de teste pronto. Isso eliminaria completamente o trabalho manual de escrever testes. O sistema resultante foi chamado de "gravador".
Interface para visualizar condições de caso de testePrincípio de funcionamento
Todos os parâmetros de solicitação (cabeçalhos HTTP, variáveis de ambiente, dados POST, se houver) e a resposta inteira são gravados no arquivo xml. Cada registro recebe um número de série. Todos os pedidos são divididos em modificação e não modificação. Após a gravação do teste, muitas das consultas não modificadoras são cortadas, pois não afetam a execução dos testes e apenas atrasam e confundem o processo de execução (daí os números de sequência ausentes na captura de tela).
Durante a gravação, o gravador permite definir a verificação de valores nos campos dos formulários e nas colunas das listas com apenas um clique. Também permite gravar testes negativos, lembrando que erro o painel retornou ao gravar um teste.
Durante a reprodução, as solicitações são enviadas diretamente para a API do aplicativo sem usar um navegador.
De fato, um gravador é um módulo que se integra a todos os nossos produtos e permite a instalação de processadores responsivos a eventos. Escrito usando o COREmanager.
Um exemplo de gravação de uma chamada feita por um gravador:
Record<params> <param name="CONTENT_LENGTH">210</param> <param name="CONTENT_TYPE">application%2Fx%2Dwww%2Dform%2Durlencoded%3B%20charset%3DUTF%2D8</param> <param name="HTTPS">on</param> <param name="HTTP_ACCEPT">text%2Fhtml%2C%20%2A%2F%2A%3B%20q%3D0%2E01</param> <param name="HTTP_ACCEPT_LANGUAGE">en%2DUS%2Cen%3Bq%3D0%2E5</param> <param name="HTTP_CACHE_CONTROL">no%2Dcache</param> <param name="HTTP_CONNECTION">keep%2Dalive</param> <param name="HTTP_COOKIE">corelang5%3Dorion%3Aru%3B%20ispmgrlang5%3Dorion%3Aru%3B%20ipmgrlang5%3Dorion%3Aru%3B%20ipmgrses5%3Dbdd69179d627%3B%20ispmgrses5%3D14157f7bbc5e%3B%20menupane%3D30%5Faccount%2D1%253A30%5Fdomains%2D1%253A30%5Fwebserver%2D1%253A30%5Fantispam%2D1%253A30%5Fmaintain%2D1%253A30%5Ftool%2D1%253A30%5Fstat%2D1%253A30%5Fsrvset%2D1%253A30%5Fsysstat%2D1%253A30%5Fintegration%2D1%253A30%5Fset%2D1%253A30%5Fmgrhelp%2D1</param> <param name="HTTP_HOST">172%2E31%2E240%2E175%3A1500</param> <param name="HTTP_ISP_CLIENT">Web%2Dinterface</param> <param name="HTTP_PRAGMA">no%2Dcache</param> <param name="HTTP_REFERER">https%3A%2F%2F172%2E31%2E240%2E175%3A1500%2Fispmgr</param> <param name="HTTP_USER_AGENT">Mozilla%2F5%2E0%20%28X11%3B%20Ubuntu%3B%20Linux%20x86%5F64%3B%20rv%3A24%2E0%29%20Gecko%2F20100101%20Firefox%2F24%2E0</param> <param name="HTTP_X_REQUESTED_WITH">XMLHttpRequest</param> <param name="QUERY_STRING"/> <param name="REMOTE_ADDR"></param> <param name="REMOTE_PORT">38640</param> <param name="REQUEST_METHOD">POST</param> <param name="REQUEST_URI">%2Fispmgr</param> <param name="SCRIPT_NAME">%2Fispmgr</param> <param name="SERVER_ADDR">172%2E31%2E240%2E175</param> <param name="SERVER_NAME">172%2E31%2E240%2E175</param> <param name="SERVER_PORT">1500</param> </params> <postdata>func%3Demaildomain%2Eedit%26elid%3D%26name%3Dtest%2Eemail%26owner%3Dusr%26ipsrc%3Dauto%26defaction%3Derror%26redirval%3D%26spamassassin%3Doff%26avcheck%3Doff%26clicked%5Fbutton%3Dok%26progressid%3Dfalse%5F1424243906672%26sok%3Dok%26sfrom%3Dajax%26operafake%3D1424243906673</postdata> <answer> <doc lang="ru" func="emaildomain.edit" binary="/ispmgr" host="https://172.31.240.175:1500" features="cba82687e7756e2c0195c88d4180f5d50" notify="0" theme="/manimg/orion/" css="main.css" logo="logo-ispmgr.png" logolink="" favicon="favicon-ispmgr.ico" localdir="default/"> <metadata name="emaildomain.edit" type="form" mgr="ispmgr" decorated="yes"> <form> <field name="name"> <input type="text" name="name" required="yes" check="domain" convert="punycode" maxlength="255"/> </field> // <buttons> <button name="ok" type="ok"/> <button name="cancel" type="cancel"/> </buttons> </form> </metadata> <messages name="emaildomain.edit" checked="cba82687e7756e2c0195c88d4180f5d5"> <msg name="currentmonth"> </msg> // </messages> <doc lang="ru" func="emaildomain.edit" binary="/ispmgr" host="https://172.31.240.175:1500" features="cba82687e7756e2c0195c88d4180f5d50" notify="0" theme="/manimg/orion/" css="main.css" logo="logo-ispmgr.png" logolink="" favicon="favicon-ispmgr.ico" localdir="default/"> <slist name="owner"> <val key="usr">usr</val> </slist> <slist name="defaction"> <val msg="yes" key="error"> </val> </slist> <slist name="ipsrc"> <val msg="yes" key="auto"> </val> </slist> <name/> <avcheck>off</avcheck> <owner>usr</owner> <ipsrc>auto</ipsrc> </doc> <id>test.email</id> <ok/> <tparams> <clicked_button>ok</clicked_button> </tparams> </doc> </answer> <localmacro> <macros name="mpre_HostIP" field="ipsrc">auto</macros> </localmacro>
Melhorias (sobre as quais não pensamos antecipadamente)
Esperando
Uma pessoa pode "apenas esperar", um computador - não. Um dos primeiros problemas que o gravador deveria resolver foi a escrita de vaters. O que o povo não inventou para esperar pela conclusão da operação. A expectativa de tarefas em segundo plano e a capacidade de adicionar uma parada na etapa especificada por um número especificado de segundos foram implementadas.
Gravando e editando etapas de teste
Provavelmente, todos se lembram dos tempos das máquinas de escrever: um erro - e você precisa reimprimir a página inteira.

Para que o testador não tenha que reescrever o teste inteiro com uma ação incorreta, foi adicionado um mecanismo para registrar novamente o teste de qualquer etapa após salvar. A capacidade de editar é útil quando você precisa adaptar o teste às mudanças no comportamento das funções testadas.
Macros para variáveis
Durante os testes, os valores nos parâmetros transmitidos e verificados foram alterados dependendo do servidor em que estavam sendo executados. Um exemplo desses dados são endereços IP. É impossível reconhecê-los no estágio de gravação do teste, então eu adicionei um sistema macro. Isso nos permitiu criar testes que não estão tão rigidamente ligados ao ambiente. A desvantagem da solução é que, após a gravação, as macros devem ser especificadas manualmente.
Outro problema que complica significativamente o trabalho com o gravador é o uso de chaves não nativas. Não o notamos imediatamente, pois testamos o gravador no ISPmanager, que usa identificadores nativos. Mas em alguns outros painéis, o registro é identificado por um ID exclusivo. Portanto, tive que ensinar o gravador não apenas a obter o identificador após a criação do registro ou objeto (já que o ID pode mudar do início ao início), mas também a substituí-lo em todas as solicitações subsequentes.
Suporte ao formato JUnit
Os testes gerados por fita são executados automaticamente no ambiente de integração contínua Jenkins. Após a conclusão dos testes, é criado um arquivo xml contendo os dados no formato JUnit. Para que o arquivo seja formado corretamente, foi introduzida uma restrição na nomeação de testes. Por exemplo, o teste User.Create.xml se enquadra no testsuite com o nome User e, portanto, possui uma caixa de teste chamada Create. No caso de um erro, um nó de falha com uma descrição completa do erro foi adicionado a ele.
Métricas
O número de funções chamadas únicas é calculado durante a passagem dos testes e a proporção é determinada como uma porcentagem do número total de funções, excluindo as disponíveis apenas para uso interno. Assim, a cobertura de teste mais simples é medida. Além disso, as métricas mostram o tempo total necessário para concluir o teste e o número de testes bem-sucedidos e com falha.
Repositório de teste
O repositório de teste resolve primeiro o problema de transferir testes prontos para outros servidores. Também é útil quando vários testadores escrevem testes. Um pequeno painel para armazenar testes de armazenamento foi desenvolvido e implantado também com base em nosso COREmanager. O gravador possui um módulo para sincronizar testes com o armazenamento. Ao escrever um novo teste ou descarregar testes do repositório, eles ficam automaticamente indisponíveis para carregamento no repositório, para que não haja confusão. Depois de alterar o teste, o número da revisão é aumentado, de modo que somente é testado após o carregamento das alterações no repositório.
Dificuldades (bem, onde sem elas)
O uso de um gravador mostrou que nem todas as funções da API seguiam nossas recomendações internas. Em particular, nem todas as funções retornaram o identificador de registro após sua criação. Eu tive que retornar, inclusive para um código de trabalho, e alinhá-lo com os requisitos.
Impasse é outro problema. O painel implica a execução de algumas ações críticas no modo exclusivo. E o gravador, fazendo parte do mesmo painel, causando essas funções, causou o congelamento de todo o sistema. Foi possível determinar isso apenas com a ajuda do GDB (ninguém se lembrava desse recurso). Infelizmente, havia muletas, porque foi decidido ao executar os testes do gravador executar essas funções no modo multithread. Teoricamente, era possível projetar um gravador não com um módulo, mas com um painel separado. Mas nós não tentamos.
Infelizmente, escrever e esquecer também falharam. A interface de nossos produtos está mudando e se tornando mais complicada. Além disso, o número de componentes da interface está crescendo ativamente. Portanto, o gravador deve ser modificado de tempos em tempos, para que, quando novos componentes apareçam, ele possa analisar sua estrutura e processar os resultados das consultas.
Conclusão
A criação de um gravador ajudou a melhorar a qualidade dos produtos testados. Economizou tempo e recursos para treinar testadores. Ao longo do caminho, o gravador nos permitiu revisar nossa API quanto à conformidade com as recomendações internas e, portanto, tornar a API um pouco mais "lógica".