Olá Habr!
Meu nome é Vitaliy Kotov, trabalho no Badoo e na maior parte do tempo lida com problemas de automação de testes. Quero compartilhar a solução para uma dessas perguntas neste artigo.
Será sobre como organizamos o processo de trabalhar testes de interface do usuário com testes A / B, dos quais temos muito. Vou falar sobre quais problemas encontramos e que inundação chegamos no final. Bem-vindo ao gato!

Até começarmos ...
A palavra teste é muito comum neste artigo. Isso porque estamos falando de testes de interface do usuário e testes A / B ao mesmo tempo. Eu sempre tentei separar esses dois conceitos e formular pensamentos para facilitar a leitura do texto. Se em algum lugar eu perdi a primeira parte da palavra e escrevi simplesmente "teste", quis dizer o teste da interface do usuário.
Boa leitura!
O que são testes A / B
Então, primeiro de tudo, vamos definir o conceito de teste A / B. Aqui está uma citação da Wikipedia:
“O teste A / B (teste Eng. A / B, teste dividido) é um método de pesquisa de marketing, cuja essência é que o grupo de elementos de controle é comparado com um conjunto de grupos de testes nos quais um ou mais indicadores foram alterados, a fim de para descobrir qual das alterações melhora o destino ” Link .
Em termos de nosso projeto, a presença de um teste A / B implica que algumas funcionalidades sejam diferentes para diferentes usuários. Eu destacaria várias opções:
- O recurso está disponível para um grupo de usuários, mas não está disponível para outro;
- O recurso está disponível para todos os usuários, mas funciona de maneiras diferentes;
- O recurso está disponível para todos os usuários, funciona da mesma forma, mas parece diferente;
- qualquer combinação das três opções anteriores.
Para que toda essa lógica funcione, temos uma ferramenta em nossa empresa chamada
UserSplit Tool , e nosso desenvolvedor Rinat Akhmadeev falou sobre isso em detalhes neste
artigo .
Falaremos agora sobre o que significa ter testes A / B para o departamento de testes e para automação em particular.
Cobertura de teste de interface do usuário
Quando falamos sobre a cobertura da interface do usuário, não estamos falando sobre o número de linhas de código que testamos. Isso é compreensível, porque apenas abrir uma página pode envolver muitos componentes, enquanto ainda não testamos nada.
Ao longo dos anos de trabalho no campo da automação de testes, vi várias maneiras de medir a cobertura dos testes de interface do usuário. Não listarei todos eles, apenas direi que preferimos avaliar esse indicador pelo número de recursos cobertos pelos testes de interface do usuário. Essa não é a maneira ideal (eu pessoalmente não conheço a maneira ideal), mas, no nosso caso, funciona.
E aqui voltamos diretamente ao tópico do artigo. Como medir e manter um bom nível de cobertura dos testes de interface do usuário, quando cada recurso pode se comportar de maneira diferente, dependendo do usuário que o usa?
Como os recursos foram cobertos pelos testes de interface do usuário inicialmente
Mesmo antes de a Ferramenta UserSplit aparecer na empresa e realmente haver muitos testes A / B, seguimos a estratégia a seguir para cobrir recursos com testes de interface do usuário: cobrindo apenas os recursos que estavam em produção há algum tempo e haviam se estabelecido.
E tudo porque antes, quando o recurso só entrou em produção, ele ainda "sintonizou" por algum tempo - seu comportamento e aparência podem mudar. E ela também não conseguiu provar a si mesma e desapareceu rapidamente dos olhos dos usuários. Escrever testes de interface do usuário para recursos instáveis é caro e não foi praticado conosco.
Com a introdução dos testes A / B no processo de desenvolvimento, nada mudou no início. Cada teste A / B tinha o chamado "grupo de controle", ou seja, um grupo que viu algum comportamento padrão do recurso. Foi nele que os testes de interface do usuário foram escritos. Tudo o que precisava ser feito ao escrever testes de interface do usuário para esse recurso era lembrar de habilitar o usuário com comportamento padrão. Chamamos esse processo de força do grupo A / B (da força inglesa).
Vou me
debruçar sobre a descrição da
força em mais detalhes, pois ela ainda desempenhará um papel na minha história.
Força para testes A / B e QaAPI
Falamos repetidamente sobre o QaAPI em nossos artigos e relatórios. No entanto, por incrível que pareça, até agora não escrevemos um artigo completo sobre essa ferramenta. Provavelmente um dia essa lacuna será preenchida. Enquanto isso, você pode assistir a um vídeo do discurso de meu colega Dmitry Marushchenko: "
4. O conceito QaAPI: uma análise dos testes do outro lado das barricadas ".
Em poucas palavras, o QaAPI permite fazer solicitações do teste para o servidor de aplicativos por meio de um backdoor especial para manipular qualquer dado. Usando essa ferramenta, por exemplo, preparamos os usuários para casos de teste específicos, enviamos mensagens a eles, carregamos fotos e assim por diante.
Usando o mesmo QaAPI, podemos forçar o grupo de teste A / B; basta indicar o nome do teste e o nome do grupo desejado. A chamada de teste é mais ou menos assim:
QaApi::forceSpliTest(“Test name”, “Test group name”, {USER_ID or DEVICE_ID});
O último parâmetro que especificamos é user_id ou device_id, para o qual essa força deve começar a trabalhar. Especificamos o parâmetro device_id no caso de um usuário não autorizado, pois o parâmetro user_id ainda não existe. É isso mesmo, para páginas não autorizadas também temos testes A / B.
Depois de chamar esse método QaAPI, é garantido que um usuário autorizado ou o proprietário do dispositivo veja a versão do recurso que forjamos. Esses são os desafios que escrevemos nos testes de interface do usuário, que cobriam recursos que estão sob teste A / B.
E assim vivemos por um longo tempo. Os testes de interface do usuário cobriram apenas grupos de controle de testes A / B. Então não havia muitos, e funcionou. Mas o tempo passou; o número de testes A / B começou a aumentar e quase todos os novos recursos começaram a ser executados nos testes A / B. A abordagem de cobrir apenas as versões de controle dos recursos parou de nos satisfazer. E aqui está o porquê ...
Por que cobrir testes A / B
Problema Um - CoberturaComo escrevi acima, ao longo do tempo, quase todos os novos recursos começaram a aparecer em testes A / B. Além do controle, cada recurso tem mais uma, duas ou três outras opções. Acontece que, para esse recurso, a cobertura, na melhor das hipóteses, não excederá 50% e, na pior, será de aproximadamente 25%. Anteriormente, quando havia poucos recursos, isso não afetava significativamente a taxa de cobertura total. Agora - começou a renderizar.
Problema dois - testes A / B longosAlguns testes A / B agora demoram um pouco. E continuamos a ser liberados duas vezes por dia (isso pode ser encontrado no artigo de Ilya Kudinov, engenheiro de controle de qualidade, “
Como sobrevivemos há dois
anos sob as condições de dois lançamentos por dia ”).
Portanto, a probabilidade de quebrar alguma versão do teste A / B durante esse período é incrivelmente alta. E isso certamente afetará a experiência do usuário e negará todo o sentido dos testes A / B do recurso: afinal, um recurso pode mostrar resultados ruins em algumas versões, não porque os usuários não gostem, mas porque não funciona conforme o esperado.
Se quisermos ter certeza do resultado do teste A / B, não devemos permitir que nenhuma versão do recurso funcione de maneira diferente do esperado.
O terceiro problema é a relevância dos testes de interface do usuárioExiste o lançamento de um teste A / B. Isso significa que o teste A / B coletou estatísticas suficientes e o gerente de produto está pronto para abrir a opção vencedora para todos os usuários. A liberação do teste A / B ocorre de forma assíncrona com a liberação do código, pois depende da configuração da configuração e não do código.
Suponha que a variante não-controle ganhou e se tornou melhor. O que acontecerá com os testes de interface do usuário que apenas o abordaram? É isso mesmo: eles vão quebrar. Mas e se eles quebrarem uma hora antes do lançamento da compilação? Podemos realizar testes de regressão dessa compilação? Não. Como você sabe, com testes quebrados, você não irá longe.
Portanto, você precisa estar preparado para fechar qualquer teste A / B com antecedência, para que não interfira no desempenho dos testes de interface do usuário e, como resultado, na próxima versão da compilação.
ConclusãoA conclusão do exposto acima é óbvia: precisamos cobrir os testes A / B com os testes de interface do usuário em sua totalidade, todas as opções. Isso é lógico? Sim Obrigado a todos, divergem!
... brincadeira! Não é tão simples.
Interface para testes A / B
A primeira coisa que pareceu inconveniente foi o controle sobre quais testes e recursos A / B já estavam cobertos e quais ainda não estavam. Historicamente, chamamos testes de interface do usuário de acordo com o seguinte princípio:
- nome do recurso ou página;
- descrição do caso;
- Teste
Por exemplo, ChatBlockedUserTest, RegistrationViaFacebookTest e assim por diante. Empurrar aqui também o nome do teste de divisão parecia desconfortável. Primeiro, os nomes se tornariam incrivelmente longos. Em segundo lugar, os testes precisariam ser renomeados no final do teste A / B, e isso teria um efeito ruim na coleta de estatísticas que leva em consideração o nome do teste da interface do usuário.
Obter o código para chamar o método QaAPI o tempo todo ainda é um prazer.
Por isso, decidimos remover todas as chamadas para QaApi :: forceSplitTest () do código dos testes de interface do usuário e transferir os dados sobre onde quais forças são necessárias para a tabela MySQL. Para ela, fizemos uma apresentação de interface do usuário no Selenium Manager (falei sobre isso
aqui ).
Parece algo como isto:

Na tabela, você pode indicar para qual teste de interface do usuário a força de qual teste A / B e em qual grupo queremos aplicar. Você pode especificar o nome do próprio teste de interface do usuário, classe de teste ou Tudo.
Além disso, podemos indicar se essa força se aplica a usuários autorizados ou não autorizados.
Em seguida, ensinamos testes de interface do usuário na inicialização para obter dados desta tabela e forçar aqueles diretamente relacionados ao teste em execução ou a todos (todos) testes.
Assim, conseguimos coletar todas as manipulações dos testes A / B em um único local. Agora é fácil visualizar a lista de testes A / B cobertos.
Lá, criamos um formulário para adicionar novos testes A / B:

Tudo isso permite que você adicione e remova com facilidade e rapidez a força necessária sem criar uma confirmação, esperando que ela se decomponha em todas as nuvens em que os testes de interface do usuário são executados etc.
Arquitetura de teste da interface do usuário
A segunda coisa que decidimos prestar atenção é uma revisão da abordagem para escrever testes de interface do usuário para testes A / B.
Em poucas palavras, mostrarei como escrevemos testes regulares da interface do usuário. A arquitetura é bastante simples e familiar:
- classes de teste - descreve a lógica de negócios do recurso abordado (de fato, estes são os scripts de nossos testes: fizeram isso, viram isso);
- Classes PageObject - todas as interações com a interface do usuário e os localizadores são descritas lá;
- Classes TestCase - existem métodos comuns que não se relacionam diretamente à interface do usuário, mas podem ser úteis em várias classes de teste (por exemplo, interação com QaAPI);
- classes principais - há a lógica de aumentar a sessão, o log e outras coisas que você não precisa tocar ao escrever um teste regular.
Em geral, essa arquitetura nos convém completamente. Sabemos que, se a interface do usuário foi alterada, apenas as classes PageObject precisam ser alteradas (enquanto os testes em si não devem ser afetados). Se a lógica de negócios de um recurso mudou, alteramos o cenário.
Como escrevi em um
artigo anterior , todo mundo trabalha com testes de interface do usuário: os caras do departamento de testes manuais e os desenvolvedores. Quanto mais simples e mais compreensível for esse processo, mais frequentemente as pessoas que não estão diretamente relacionadas a eles executam testes.
Mas, como escrevi acima, diferentemente dos recursos bem estabelecidos, os testes A / B vêm ou vão. Se os escrevermos no mesmo formato dos testes regulares da interface do usuário, teremos que remover permanentemente o código de muitos lugares diferentes após a conclusão dos testes A / B. Você entende que, para refatoração, especialmente quando tudo funciona sem ela, nem sempre é possível alocar tempo.
No entanto, não queremos deixar nossas classes sobrecarregadas com métodos e localizadores não utilizados, isso tornará os mesmos objetos de página difíceis de usar. Como facilitar sua vida?
Então o PhpStorm veio em nosso socorro (graças aos caras do JetBrains pelo conveniente IDE), ou seja, esse
recurso .
Em resumo, permite o uso de tags especiais para dividir o código nas chamadas regiões. Tentamos - e gostamos. Começamos a escrever testes de interface do usuário temporários para testes A / B ativos em um arquivo, dividindo as zonas de código em regiões indicando a classe na qual esse código deve ser colocado no futuro.
Como resultado, o código de teste ficou mais ou menos assim:

Em cada região, há um código que pertence a uma classe específica. Certamente, em outros IDEs, há algo semelhante.
Assim, cobrimos todas as variantes do teste A / B com uma classe de teste, colocando os métodos e localizadores PageObject lá. E após sua conclusão, removemos primeiro as opções perdedoras da classe e depois distribuímos com facilidade o código restante nas classes desejadas, de acordo com o que é indicado na região.
Como encerramos agora os testes A / B
Você não pode simplesmente fazer e cobrir todos os testes A / B com testes de interface do usuário de uma só vez. Por outro lado, não existe tal tarefa. O desafio em termos de automação é cobrir rapidamente apenas testes importantes e de longa execução.
No entanto, antes do lançamento de qualquer teste A / B, mesmo o menor, eu quero poder executar todos os testes de interface do usuário na versão vencedora e garantir que tudo funcione como deveria e replicaremos a funcionalidade de trabalho de alta qualidade para 100% dos usuários.
A solução mencionada acima com uma tabela MySQL não é adequada para essa finalidade. O fato é que, se você adicionar força lá, ele começará imediatamente a ser ativado em todos os testes de interface do usuário. Além da preparação (nosso ambiente de pré-produção, onde executamos um conjunto completo de testes), isso também afetará os testes de interface do usuário lançados em relação às ramificações de tarefas individuais. Os colegas do departamento de testes manuais trabalharão com os resultados desses lançamentos. E se um teste A / B com falha tiver um bug, os testes para suas tarefas também serão reprovados e os funcionários poderão decidir que o problema está em sua tarefa, e não no teste A / B. Por esse motivo, testes e testes podem levar muito tempo (ninguém ficará satisfeito).
Até o momento, conseguimos gerenciar alterações mínimas, adicionando a capacidade de especificar o ambiente de destino na tabela:

Esse ambiente pode ser alterado rapidamente em um registro existente. Portanto, podemos adicionar força apenas para a preparação, sem afetar os resultados da aprovação em testes em tarefas individuais.
Resumir
Portanto, antes do início desta história, nossos testes de interface do usuário cobriam apenas os grupos principais (de controle) dos testes A / B. Mas percebemos que queremos mais e chegamos à conclusão de que também é necessário cobrir outras versões dos testes A / B.
Em resumo:
- criamos uma interface para controle conveniente sobre a cobertura dos testes A / B; Como resultado, agora temos todas as informações sobre a operação de testes de interface do usuário com testes A / B;
- desenvolvemos para nós mesmos uma maneira de escrever testes de interface do usuário temporários com um fluxo simples e eficaz para sua remoção ou transferência para as fileiras do permanente;
- aprendemos como testar com facilidade e sem esforço as liberações de testes A / B, sem interferir em outros testes de interface do usuário em execução e sem confirmações desnecessárias no Git.
Tudo isso tornou possível adaptar a automação de testes a recursos em constante mudança, controlar e aumentar facilmente o nível de cobertura e não sobrecarregar com um código legado.
Você tem alguma experiência em trazer à primeira vista uma situação caótica para alguma ordem controlada e simplificar sua vida para você e seus colegas? Compartilhe nos comentários. :)
Obrigado pela atenção! E Feliz Ano Novo!