
Olá, sou Alexey, desenvolvedor full-stack da plataforma Vimbox. Quando cheguei a Skyeng, eles decidiram aqui se valia a pena gastar no sistema de autoteste e me pediram para compartilhar minha experiência com o trabalho anterior. E eu tive essa experiência: quando deixamos o lugar anterior, escrevemos em php e giramos mais de 3 mil testes. Como resultado, fiz uma pequena apresentação interna falando sobre o rake que consegui em alguns anos desenvolvendo esses auto-testes, lutando pela velocidade, legibilidade do código e eficiência geral. A apresentação parecia útil para os colegas, então eu a coloquei no texto para ser útil também para um público mais amplo.
Para começar, os termos que serão discutidos no artigo:
- Teste de aceitação - teste de ponta a ponta: aqui o navegador ou emulador de navegador executa o script
- Teste de unidade ( teste de unidade) - teste de método
- Teste funcional - um teste de um controlador ou componente, quando se trata de front-end
- Fixação - o estado do ambiente de teste necessário para o teste funcionar (variáveis globais, dados no banco de dados e outros participantes no script de teste)
Prós e contras de diferentes tipos de testes

Testes de aceitação
- Prós: óbvio pelo nome, esses testes abrangem todo o sistema de cima para baixo, verifique se tudo funciona como deveria.
- Contras: o feedback desses testes é muito lento, eles funcionam por um longo tempo, não são muito confiáveis, existem muitos falsos positivos. Em um trabalho anterior, também fomos confrontados com o fato de que os drivers da Web não detectaram alguns dos elementos que vimos com nossos olhos. Agora isso provavelmente foi consertado, mas tive que abandoná-los.
Testes unitários
- Prós: fácil de escrever, trabalhar rapidamente. Eles abrangem um pequeno pedaço de código; você não precisa de muitos estados; portanto, também não precisa de um equipamento grande.
- Contras: instável a alterações na arquitetura ou estrutura interna do código. Se você precisar mesclar dois métodos em um ou separar, selecione uma classe, exclua um método e precisará reescrever os testes.
Os testes funcionais são uma solução intermediária.
- Prós: aceitação mais confiável, mais resistente a mudanças na estrutura do código do que modular.
- Contras: mais lento que o modular, mais difícil de escrever, porque precisa preparar um grande acessório.
A luta pela velocidade
No trabalho antigo, escrevemos muitos testes funcionais, e o principal desafio foi a velocidade de resposta. Eu tive que esperar muito tempo pelo resultado, mesmo com um lançamento local no computador do desenvolvedor. A velocidade era tão lenta que não era possível aplicar a abordagem "desenvolvimento através de testes", pois envolve a execução de autotestes várias vezes por hora. Encontrou um gargalo - trabalhando com o banco de dados. Como lidar com isso?
Experimente primeiro: moki
Mock no PhpUnit é um objeto criado dinamicamente cuja classe é herdada dinamicamente da classe parodiada. Você pode configurar o que os métodos do mok retornarão, verificar quais métodos do moq quantas vezes com quais parâmetros foram chamados
A principal vantagem do moki - eles permitem que você corte peças inteiras de funcionalidade. Substituindo o serviço pelo moch, eliminamos a necessidade de pensar no que está acontecendo lá, de desenvolver scripts e acessórios adicionais para que tudo funcione corretamente. Como resultado: menos equipamentos, e a velocidade de resposta é maior devido ao fato de cortarmos o código extra que executa as consultas no banco de dados.
A vantagem implícita dos mobs é que eles melhoram a organização dos vícios. Quando você escreve um código, sabendo que será necessário escrever um teste, onde algo é substituído por mokami, você pensa imediatamente nas dependências.
Menos : o código de teste está muito anexado à implementação. Durante o teste, devemos criar um objeto simulado e pensar sobre quais métodos devem ser chamados nele.
O segundo menos encontrado é que os testes se tornaram menos confiáveis. Eles “não percebem” nem mesmo alterações na interface, para não mencionar a implementação. I.e. nós excluímos o método em algum lugar e, depois de muito tempo, descobrimos que os testes que o cobriam ainda funcionavam como se nada tivesse acontecido, porque vimos sua simulação, e ele fingiu que estava tudo bem.
Considero a experiência com mokas malsucedida em termos de acelerar os testes.
Experiência dois: SQLite
A próxima opção é o SQLite DBMS , ele pode criar um banco de dados na RAM. Eu tive que escrever um esquema do tradutor PostgreSQL no SQLite, após cada migração, um novo esquema SQLite era gerado. Os testes deste circuito criaram um banco de dados vazio na RAM. Essa abordagem aumentou a velocidade dos testes em máquinas locais em duas a quatro vezes. Tornou-se realista executar todo o conjunto de testes várias vezes por hora.
Mas havia contras. Perdemos muitos dos recursos nativos do PostgreSQL (json, algumas funções agregadas úteis e muito mais). As consultas precisavam ser escritas para que funcionassem no PostgreSQL e no SQLite.
Experiência três: otimização do PostgreSQL
Esta decisão estava funcionando, mas causou alguma dor. Em algum momento, aprendemos que o PostgreSQL pode ser otimizado para autotestes, o que reduz o tempo de resposta em cerca de quatro vezes. Para fazer isso, adicione algumas configurações ao postgresql.conf:
fsync=off synchronous_commit=off full_page_writes=off
Essas são configurações de confiabilidade, que garantem que, se o servidor morrer no meio de uma transação, ele será concluído corretamente quando tudo começar a funcionar novamente. É claro que essas configurações não podem ser feitas na produção, mas era conveniente em testes automáticos.
Essa configuração é aplicada a todo o cluster, afeta todos os bancos de dados, não pode ser aplicada a nenhum banco de dados. Se você conseguir localizar os bancos de dados em um cluster separado e desativar o fsync, isso é muito conveniente.
Um pouco sobre o new
Gostaria também de mencionar o perigo do new
operador. Os serviços criados com sua ajuda não podem ser substituídos por mokas e stubs. Conclusão:
- Não use
new
para criar objetos que são essencialmente serviços. - Pode ser usado em fábricas, porque podem ser substituídos. Mas as próprias fábricas não devem ser criadas através de
new
. - Pode ser usado para criar modelos, entidades, DTO (objeto de transferência de dados), objetos de valor.
Conclusões de três anos de experiência
- No trabalho anterior, recusamos os testes de aceitação, mas agora eu os tentava novamente: provavelmente muitos bugs foram corrigidos nos drivers da web.
- Se você precisar cobrir novas funcionalidades com testes, precisará escrever apenas testes funcionais de controladores / componentes. Nesta situação, temos um alto risco de alterações estruturais, os testes de unidade são instáveis para eles.
- Não deve haver muitos testes desse tipo, porque muitos == lentamente, eles não funcionam tão rápido quanto os modulares. Vale a pena cobrir apenas os casos que podem "disparar" (eles têm a probabilidade de um erro no futuro).
- Os testes de unidade são escritos em métodos ricos em algoritmos (lógica complexa que precisa ser testada) ou em métodos com pouco risco de alterações estruturais no futuro.
- Contras de moka geralmente excedem os profissionais. Faz sentido usá-los apenas como uma substituição de gateways em APIs externas e, às vezes, serviços de código legado, que são muito difíceis de testar.
- Se você decidir escrever código sem um teste, é aconselhável pensar: "quando você o criar, e se, no futuro, ainda quisermos escrever um teste para ele?"
- Os testes devem ser fáceis e agradáveis de escrever, eles oferecem confiabilidade, confiança, ajudam a entender melhor o código, gerenciam dependências.
- Preste atenção à legibilidade dos testes. É preciso se relacionar com o código de teste da mesma maneira que o código que ele cobre.
- Acessórios de DB - parte do teste, também devem ser legíveis