Escalando testes do Android em Odnoklassniki



Olá! Meu nome é Roman Ivanitsky, trabalho na equipe de automação de testes do Odnoklassniki. OK é um serviço enorme, com mais de 70 milhões de usuários. Se falamos de dispositivos móveis, a maioria usa OK.RU em smartphones com Android. Por esse motivo, levamos muito a sério o teste de nosso aplicativo Android. Neste artigo, contarei a história do desenvolvimento de testes automatizados em nossa empresa.

2012, Odnoklassniki, a empresa está experimentando um aumento ativo no número de usuários e um aumento no número de recursos do usuário. Para atender aos objetivos de negócios, foi necessário diminuir o ciclo de liberação, mas isso foi prejudicado pelo fato de que todas as funcionalidades foram testadas manualmente. A solução para esse problema veio por si só - precisamos de autotestes. Assim, em 2012 em Odnoklassniki, uma equipe de automação de testes apareceu e o primeiro passo foi começar a escrever testes.

Um pouco de história


Os primeiros autotestes em Odnoklassniki foram escritos em Selenium. No lançamento, eles criaram Jenkins, Selenium Grid com Selenium Hub e um conjunto de Selenium Node.

Solução rápida, início rápido, lucro rápido - perfeito.

Com o tempo, o número de testes aumentou e os serviços auxiliares apareceram - por exemplo, serviços de inicialização, serviço de relatório, serviço de dados de teste. No final de 2014, tínhamos mil testes realizados em quinze a vinte minutos. Isso não nos convinha, pois estava claro que o número de testes aumentaria e, com isso, o tempo necessário para executá-los aumentaria.

Naquela época, a infraestrutura de testes automatizados era assim:



No entanto, com uma quantidade de Nó Selênio maior ou igual a 200, o Hub não conseguiu lidar com a carga. Agora, esse problema já foi estudado, e é por isso que ferramentas como o Zalenium ou o Selenoid favorito de todos apareceram. Mas em 2014 não havia solução padrão, então decidimos fazer a nossa.

Definidos os requisitos mínimos que o serviço deve atender:

  1. Escalabilidade. Não queremos depender das limitações do Selenium Hub.
  2. Estabilidade. Em 2014, o Selenium Hub não era famoso por sua operação estável.
  3. Tolerância ao erro. Precisamos da capacidade de continuar o processo de teste no caso de uma falha do datacenter ou de qualquer um dos servidores.

Assim, nossa solução para dimensionar o Selenium Grid apareceu, consistindo de um coordenador e gerenciadores de nós, aparentemente muito semelhantes ao Selenium Grid padrão, mas com seus próprios recursos. Esses recursos serão discutidos mais adiante.

Coordenador




De fato, é um intermediário de recursos (os recursos são entendidos como navegadores). Possui uma API externa através da qual os testes enviam solicitações de recursos. Essas consultas são salvas no banco de dados como tarefas a serem executadas. O coordenador sabe tudo sobre a configuração do nosso cluster - quais gerenciadores de nós existem, que tipos de recursos esses gerenciadores de nós podem fornecer, o número total de recursos, quantos recursos estão envolvidos no momento nas tarefas. Ao mesmo tempo, ele monitora os recursos - atividade, estabilidade e, nesse caso, notifica os responsáveis.

Uma característica do coordenador é que ele integra todos os gerenciadores de nós nos chamados farms.

É assim que a fazenda se parece. Mais da metade dos recursos são usados ​​e todos os nós online:



Você também pode exibir nós offline ou inseri-los em rotação em uma determinada porcentagem; isso é necessário se for necessário reduzir a carga em um nó específico.

Cada farm pode ser combinado com outros em uma unidade lógica, que chamamos de serviço. Ao mesmo tempo, um farm pode ser incluído em vários serviços diferentes. Em primeiro lugar, possibilita definir limites e priorizar os recursos utilizados por cada serviço específico. Em segundo lugar, ele permite que você gerencie facilmente a configuração - temos a capacidade de adicionar o número de gerenciadores de nós no serviço imediatamente ou vice-versa para removê-los do farm para poder interagir com esses gerenciadores de nós, por exemplo, configurar ou atualizar etc. .



A API do coordenador é bastante simples: é possível solicitar o serviço para a quantidade atual de recursos utilizados, obter seu limite e iniciar ou interromper algum recurso.

Gerenciador de nós


Este é um serviço que pode fazer duas coisas bem: receber tarefas do coordenador e lançar alguns recursos sob demanda. Por padrão, ele é projetado para que cada inicialização do recurso seja isolada, ou seja, nenhuma das ativações anteriores possa afetar o lançamento de testes subsequentes. Como resposta, o coordenador usa um monte de host e um conjunto de portas elevadas. Por exemplo, o host no qual o servidor Selenium foi iniciado e sua porta.



No host, fica assim: o serviço do gerenciador de nós está em execução e gerencia todo o ciclo de vida do recurso. Ele pega os navegadores, os conclui, garante que eles não sejam esquecidos de fechar. Para garantir o isolamento um do outro, tudo isso acontece em nome do usuário do serviço.

Interação


O teste interage com a infraestrutura descrita acima, da seguinte maneira: ele aborda o coordenador com uma solicitação dos recursos necessários, o coordenador salva essa tarefa como exigindo execução.

O gerenciador de nós, por sua vez, recorre ao coordenador de tarefas. Tendo recebido a tarefa, ele inicia o recurso. Depois disso, ele envia o resultado do lançamento ao coordenador, as partidas com falha também são relatadas ao coordenador. O teste recebe o resultado da solicitação de recurso e, se for bem-sucedido, começa a trabalhar diretamente com o recurso.



As vantagens dessa abordagem são reduzir a carga no coordenador, ganhando a capacidade de trabalhar diretamente com o recurso. Contras - a necessidade de implementar a lógica da interação com o coordenador dentro das estruturas de teste, mas para nós isso é aceitável.
Hoje, podemos rodar mais de 800 navegadores em paralelo em três data centers. Para o coordenador, esse não é o limite.

A tolerância a falhas é garantida pelo lançamento de várias instâncias do coordenador encerradas atrás do firewall do DNS em diferentes datacenters. Isso garante o acesso à instância de trabalho no caso de um problema com o datacenter ou servidor.

Como resultado, obtivemos uma solução que atendeu a todos os requisitos inicialmente definidos. Está em operação constante desde 2015 e provou sua eficácia.

Android


Quando se trata de testar no Android, geralmente existem duas abordagens principais. O primeiro é usar o WebDriver - é assim que o Selendroid e o Appium funcionam. O segundo - ao trabalhar com ferramentas nativas, implementou o Robotium, o UI Automator ou o Espresso.

As semelhanças fundamentais entre essas abordagens são obter o dispositivo e o navegador.

Existem muito mais diferenças, a principal delas é a necessidade de instalar o APK testado, com o qual levaremos artefatos na forma de logs, capturas de tela etc. e também, o fato de o teste ser realizado no próprio dispositivo, e não no IC.

Em 2015, o Odnoklassniki começou a cobrir seu aplicativo Android com autotestes. Selecionamos uma máquina Linux, conectamos um dispositivo real via USB e começamos a escrever testes no Robotium. Esta solução simples permitiu obter resultados rapidamente.

O tempo passou, o número de testes e o número de dispositivos aumentaram. Para resolver tarefas de gerenciamento, o Gerenciador de Dispositivos foi criado - um wrapper sobre comandos adb (Android Debug Bridge), que permite que a interface http api os execute.

Foi assim que a primeira API do Gerenciador de dispositivos foi exibida - com sua ajuda, você pode obter uma lista de dispositivos, instalar / desinstalar APKs, executar testes e obter resultados.



No entanto, percebemos que os resultados do teste são degradados na inicialização no servidor ADB ao qual mais de um dispositivo está conectado. A solução que nos ajudou a melhorar a estabilidade foi encontrada no isolamento de cada servidor ADB usando o Docker.

O farm está pronto - você pode conectar telefones.



Muitos estão familiarizados com esta imagem. Ouvi dizer que, se você está envolvido em fazendas Android, está como se estivesse no inferno todos os dias.



Um emulador do Android veio em nosso auxílio. Seu uso foi devido a dois fatores: primeiro, na época, já havia atingido o nível necessário de estabilidade; em segundo lugar, não tínhamos nenhum recurso que dependesse especificamente do ferro em nossos testes. Além disso, esse emulador foi bem projetado na infraestrutura existente naquele momento. O próximo passo foi ensinar o gerenciador de nós a lançar novos tipos de recursos.

O que é necessário para executar o emulador do Android?

Primeiro, você precisa de um SDK do Android com um conjunto de utilitários.

Então você precisa criar o AVD - Dispositivo Virtual Android - é assim que seu emulador Android será organizado - qual arquitetura ele terá, quantos núcleos ele usará, se os serviços do Google estarão disponíveis etc.



Depois disso, você precisa selecionar o nome do AVD criado, definir os parâmetros, por exemplo, transferir a porta na qual o ADB será iniciado e iniciar.

No entanto, existe uma peculiaridade nesse esquema - o sistema permite executar apenas um emulador de instância em um AVD específico.

A solução para esse problema foi criar um AVD básico, armazenado na memória, possibilitando copiá-lo em outro lugar. Durante o lançamento do emulador do Android, o AVD base foi copiado para um diretório temporário mapeado na memória, após o qual foi iniciado. Esse esquema funcionou rapidamente, mas era complicado. Até o momento, esse problema foi resolvido pela opção somente leitura, que permite executar emuladores Android em quantidades ilimitadas a partir de um AVD

atuação


Com base nos resultados do trabalho com AVD, desenvolvemos várias recomendações internas:

  1. 86 , ARM . dev/kvm Linux HAXM- Mac Windows
  2. GPU- . , . , , , Android-
  3. .
  4. , localhost,

Quanto às imagens do Docker para teste no Android, quero destacar Agoda e Selenoid, eles usam os recursos dos emuladores do Android ao máximo.

A diferença entre eles é que, no Selenoid padrão, você tem Appium , Agoda e usou o emulador "limpo". Além disso, o Selenoid tem mais apoio da comunidade.

No final de 2018, o CloudNode-Manager foi criado, entra em contato com o coordenador, recebe tarefas e é iniciado usando comandos na nuvem. Em vez de máquinas de ferro, este serviço usa os recursos da nuvem única - a nuvem privada de Odnoklassniki.

Conseguimos alcançar o dimensionamento ensinando o DeviceManager a trabalhar com o Coordenador. Para fazer isso, tive que alterar a API do gerenciador de dispositivos para adicionar a capacidade de solicitar um tipo de dispositivo (virtual / real).

É o que acontece se você tentar executar a Instalação do ADB em 250 emuladores a partir de uma única máquina.



Os atendentes reagiram imediatamente a isso e iniciaram um incidente - a máquina carregava a interface de rede gigabit com tráfego de saída. Essa complexidade foi resolvida aumentando a taxa de transferência no servidor. Não posso dizer que esse problema nos deu muitos problemas, mas você não deve esquecer.

Parece que o sucesso é o Gerenciador de dispositivos, coordenador, dimensionamento. Podemos executar testes em todo o farm. Em princípio, podemos executá-los em todas as solicitações de recebimento, e o desenvolvedor receberá rapidamente um feedback.



Mas nem tudo é tão róseo. Você deve ter notado que até agora nada foi dito sobre a qualidade dos testes.



Era assim que eram os nossos lançamentos. E o mais interessante é que entre os lançamentos, testes completamente diferentes podem cair. Foram quedas instáveis. E nem eu, nem os desenvolvedores, nem os testadores confiamos nesses resultados.

Como lidamos com esse problema? Eles apenas copiaram tudo, de Robotium a Espresso, e ficou bom ... Na verdade, não.

Para resolver esse problema, não apenas reescrevemos tudo no Espresso, mas também começamos a usar a API para todos os tipos de ações, como upload de fotos, criação de postagens, adição de amigos, etc., fizemos um login rápido, utilizamos diplinks que permitem acessar diretamente a tela desejada e, é claro, analisamos todos os casos de teste.

Agora, os testes são parecidos com o seguinte:



Você pode observar que os testes em vermelho permanecem, mas é importante lembrar que esses são testes de ponta a ponta que são executados na produção. Temos um limite no número de testes que podem cair no ramo principal do aplicativo.

Agora, temos testes e dimensionamento estáveis. No entanto, a infraestrutura de teste ainda está altamente ligada aos testes. Ao mesmo tempo, devido à expectativa de testes de ponta a ponta, o IC está ocupado e outros assemblies podem enfileirar-se, aguardando agentes livres. Além disso, não há um esquema claro para trabalhar com partidas paralelas.

Os motivos mencionados acima tornaram-se o ímpeto para o desenvolvimento do QueueRunner - um serviço que permite executar testes de forma assíncrona, sem bloquear o IC. Para funcionar, ele precisa de um teste e um APK, além de um conjunto de testes. Depois de receber os dados necessários, ele poderá organizar execuções de execuções na fila, alocando e liberando os recursos necessários. O QueueRunner baixa os resultados da execução para Jira e Stash e também os envia por correio e no messenger.

O QueueRunner possui um fluxo de teste - monitora o ciclo de vida do teste. O fluxo padrão que usamos agora consiste em cinco etapas:

  1. Dispositivo receptor. Nesse ponto, o Gerenciador de dispositivos solicita ao coordenador um dispositivo real ou virtual.
  2. . APK , – , .
  3. ,

Como resultado, cinco etapas simples são o ciclo de vida completo do teste em nosso serviço.



Que benefícios o QueueRunner nos deu? Em primeiro lugar, ele usa todos os recursos possíveis ao máximo - pode ser dimensionado para todo o farm e obter resultados rapidamente. Em segundo lugar, com o bônus, tivemos a oportunidade de controlar a sequência de testes. Por exemplo, podemos executar os testes mais longos ou mais problemáticos no início e, assim, reduzir o tempo que leva para esperar a execução.

O QueueRunner também permite fazer retrays inteligentes. Armazenamos todos os dados no banco de dados, para que a qualquer momento possamos ver o histórico do teste. Por exemplo, é possível examinar a proporção de aprovações com e sem êxito do teste e decidir se, em princípio, vale a pena reiniciar o teste.

O QueueRunner e o Devicemanager nos deram a capacidade de se adaptar à quantidade de recursos. Agora podemos escalar para todo o farm, graças ao uso de emuladores, ou seja, um número quase ilimitado de dispositivos virtuais nos deu a oportunidade de executar muito mais testes, mas se por algum motivo os recursos não estiverem disponíveis, o serviço esperará que eles retornem e não haverá perda de lançamentos. Utilizamos apenas os recursos disponíveis, portanto, após algum tempo os resultados ainda serão obtidos e, ao mesmo tempo, o IC não será bloqueado. E o mais importante, a infraestrutura e os testes de teste agora estão separados.
Agora, para executar testes no Android, você só precisa nos fornecer um APK de teste e uma lista de testes.

Percorremos um longo caminho desde o farm Selenium em máquinas virtuais até o lançamento de testes do Android na nuvem. No entanto, esse caminho ainda não foi concluído.

Processo de desenvolvimento


Vamos ver como a infraestrutura de teste está relacionada ao processo de desenvolvimento e como os testadores e desenvolvedores a veem.

Nossa equipe do Android usa o GitFlow padrão:



cada recurso tem sua própria ramificação. O principal desenvolvimento ocorre no ramo de desenvolvimento. Um desenvolvedor que decide criar um novo super recurso começa seu desenvolvimento em sua própria ramificação, enquanto outros desenvolvedores podem trabalhar em outras ramificações em paralelo. Quando um desenvolvedor considera que o melhor código ideal e bonito do mundo está pronto e precisa ser implementado para os usuários o mais rápido possível, ele faz uma solicitação de pull no desenvolvimento, a unidade é montada automaticamente, testes de unidade e testes de componentes são executados. Simultaneamente, os APKs são montados, enviados ao QueueRunner e os testes de ponta a ponta são executados. Depois disso, os resultados da execução dos testes chegam ao desenvolvedor.

No entanto, existe uma alta probabilidade de que, após a criação da ramificação do recurso em desenvolvimento, houvesse muitos commits. Isso significa que desenvolver pode não ser o que costumava ser. Portanto, a pré-mesclagem acontece primeiro - mesclamos o desenvolvimento para o ramo de recursos atual e é nesse estado prematuro que construímos, testes de unidade, testes de componentes, ponta a ponta e, com base nesses resultados, fazemos um relatório. Assim, entendemos como o recurso é funcional na versão atual do develop e, se tudo estiver correto, ele será enviado aos usuários.



Comunicando


É assim que os relatórios do Stash se parecem:



Nosso bot primeiro escreve que os testes foram iniciados e, quando são aprovados, atualiza a mensagem e adiciona quantos passaram, quantos caíram, quantos erros conhecidos e quantos testes são escamosos. Ele escreve a mesma coisa em Jira e adiciona um link para uma comparação de lançamentos.

É assim que a comparação dos dois lançamentos se parece:



Aqui, a execução atual no ramo de recursos é comparada com a última execução no desenvolvimento. Ele contém informações sobre o número de testes que estão sendo executados, problemas de correspondência, testes descartados e testes instáveis ​​instáveis ​​que estavam em um estado e alternaram para outro.

Se pelo menos um teste de unidade ou mais do que um limite de testes de ponta a ponta cair, a mesclagem será bloqueada.

Para entender se os testes caem de forma estável, comparamos os hashes dos traços das quedas, antes que eles sejam eliminados preliminarmente dos números, apenas os números das linhas permanecem. Se os hashes corresponderem, então será a mesma queda, se forem diferentes, provavelmente as quedas serão diferentes.

Sumário


Como resultado, implementamos uma solução estável e tolerante a falhas que se adapta bem à nossa infraestrutura. Em seguida, a infraestrutura resultante foi adaptada para testes do Android. Nós fomos ajudados nisso pelo gerenciador de dispositivos, que nos ajuda a trabalhar simultaneamente com dispositivos reais e virtuais, assim como pelo QueueRunner, que nos ajudou a separar a infraestrutura e os testes, e não bloquear o IC durante os testes.

Parecia o tempo de execução do teste por uma semana em 2016 - a partir de cinquenta minutos ou mais.



É assim que parece agora:



este gráfico mostra as execuções ocorridas ao longo de 2 horas de um dia útil médio. O tempo de execução foi reduzido para um máximo de 15 minutos, o número de execuções aumentou acentuadamente.

All Articles