Estratégia de testes no Android — Parte 1

Natan Ximenes
7 min readMar 1, 2022
Estátua de Sun Tzu, autor do livro A Arte da Guerra

"A estratégia sem tática é o caminho mais lento para a vitória. Tática sem estratégia é o ruído antes da derrota." (Sun Tzu, 500 a.c)

O conceito de estratégia

(do grego, stratigiki, do francês, stratégie, do inglês, strategy…)

Sun Tzu foi um grande general militar e filósofo chinês que ficou conhecido ao publicar o livro "A Arte da Guerra". O livro fala, de forma bem direta, sobre como ser eficiente no campo de batalha, através da aplicação das técnicas mais apropriadas para cada situação, seguindo uma estratégia.

O dicionário Michaelis define a palavra estratégia assim:
Arte de utilizar planejadamente os recursos de que se dispõe ou de explorar de maneira vantajosa a situação ou as condições favoráveis de que porventura se desfrute, de modo a atingir determinados objetivos.

Ou seja, a estratégia se resume em planejamento para alcançar eficiência.

Estratégia de testes

O planejamento de uma estratégia de testes não é diferente do conceito geral da palavra.

É necessário ter um plano claro sobre o que queremos testar e como queremos testar, considerando um olhar independente de vieses, focado nas particularidades do software e nos seus possíveis pontos de falha.

O que queremos testar…

Se aplica às partes do sistema que queremos estimular, para garantir seu funcionamento esperado. Partes essas que variam de acordo com o tipo de aplicação, juntamente com sua estrutura de design e arquitetura. Pode ser uma classe, função, uma camada da aplicação, uma tela, etc.

É possível testar partes de uma aplicação de forma isolada ou integrada, dependendo de como queremos testa-las.

Como queremos testar…

Se aplica tanto ao tipo de teste que será escolhido, quanto às ferramentas que ajudarão o time a atingir o objetivo.

Normalmente os tipos de testes, se resumem a esses 3: Teste Unitário, Teste de Integração, e Teste Funcional(ou simplesmente Teste de UI). Isso não quer dizer que eles são os únicos tipos de testes e também não significa que todos eles serão aplicados. Isso vai depender do tipo de arquitetura e também da aplicação.

As ferramentas envolvidas, ajudam no processo de automação, controle e asserção de uma aplicação. Através dessas ferramentas(algumas vezes nativas do próprio framework Android), é possível alternar entre os estados possíveis de uma aplicação, para realizar suas validações.

Um olhar independente de vieses…

Está relacionado a definir uma estratégia, baseada na impessoalidade da implementação, focada em validar os caminhos que um algoritmo pode seguir.

Uma estratégia não deve se basear na facilidade que um pequeno grupo de pessoas podem ter ao implementar um algoritmo, de forma “segura” de que ele vai funcionar, onde as validações seriam feitas apenas em pontos chave. Ainda que só existam pessoas especialistas desenvolvendo um sistema, ainda seriam pessoas, e seres humanos são sujeitos a falhas.

Testes devem ser criados para validar códigos, independente de quem os tenha implementado.

Qualidade não pode ser presumida, ela precisa ser garantida

Assim, a decisão do que testar e como testar deve ser tomada pelo time, baseada em fatos e não em gostos ou características pessoais.

Por exemplo: uns podem utilizar o padrão AAA(Arrange, Act, Assert) na hora de escrever um teste, outros podem usar o padrão GWT(Given, When, Then), uns podem usar libs de asserção, outros podem utilizar uma validação sem necessidade de lib e etc...
E aí, na hora de revisar PRs, vem a duvida: Qual é o jeito certo?
Então, perde-se tempo, tendo uma discussão(que muitas vezes é repetitiva), sobre um algo que já poderia ter sido acordado e definido na estratégia do time.
Alguma vezes, também pode acontecer de implementações muito distintas passarem despercebidas nos PRs, e o código de teste virar uma bagunça, onde cada canto usa uma lib ou padrão diferente. Isso pode ser um pesadelo para quem é novo no time e passa a lidar com uma base de código legada e inconsistente.

A falta de um padrão ou de um “norte” a seguir, acaba afetando diretamente a legibilidade e a manutenibilidade dos testes. É necessário ter uma documentação que sirva de guia, tanto para escrita quanto manutenção dos testes.

Ou seja, a definição de uma estratégia de teste pode ser, também, um acordo do time, que colabora para construir testes de forma mais limpa, diminuindo a carga cognitiva de quem vai implementar testes, e as incertezas como, por exemplo: Qual padrão usar? Testo isso unitariamente ou de forma integrada? Qual lib usar? etc…

Particularidades do software

Uma boa estratégia deve considerar particularidades do sistema, que podem estar relacionadas a concorrência e paralelismo, arquitetura ou aos frameworks, utilizados no desenvolvimento.

No caso do Android, os Ciclos de vida de Fragments e Activities são bons exemplos dessas particularidades. Dependendo da implementação, pode ser necessário se preocupar em colocar a activity/fragment num estado especifico do ciclo de vida, para realizar uma validação.

Um outro bom exemplo, também do Android, está relacionado a Concorrência. A escolha da solução de concorrência, para lidar com threads, vai interferir diretamente em alguns tipos de testes automatizados, tanto na perceptiva de execução(Testes Instrumentados/Integrados), onde seria necessário realizar uma configuração para sincronia de threads, como também perspectiva de validação, em relação a mocks e asserções(Testes Unitários), onde seria necessario o auxilio da lib de concorrencia para realizar asserções e mocks em funções que realizem tarefas em background.

Se o seu projeto utiliza RxJava, Coroutines, AsyncTasks(👀), ou lida com Thread de forma manual, cada uma dessas alternativas vai ter uma forma diferente de implementação do código de produção, como também no setup e nas asserções do teste.

Ou seja, são particularidades de projeto, que precisam ser levadas em consideração também no ponto de vista de testes, para que a configuração apropriada seja feita, para viabilizar as validações. Essa configuração pode envolver o uso de mocks, fakes ou abstrações, para controlar partes do código que são difíceis de validar.

Se a adoção de um framework, lib ou arquitetura é boa para o código de produção, mas adiciona complexidade ou inviabiliza a implementação de teste, talvez essa adoção precise ser revista.

Pontos de falha

Dependendo do que está sendo implementado, algumas vezes, o ato de implementar testes automatizados, para validar aquilo que criamos, não necessariamente vai refletir uma garantia de qualidade por completo.

Existem partes do sistema que são mais críticas do que outras, dependendo do que se trata o Produto cujo qual o app representa. Ou seja, se uma parte crítica apresentar muitas falhas, isso poderia representar uma perda financeira ou algum outro reflexo negativo para a empresa.

Exemplos:

  • Se estamos construindo um app que consumirá streaming de audio/video, de que adianta ter 100% de cobertura, todos os testes passando, se a stream não é executada de forma estável o suficiente, por conta de alguns detalhes de implementação(no app ou no backend), que passam despercebidos pela automação?
  • Se tenho um app que realiza operações financeiras, é aceitável, para o usuário, a presença de bugs que atrapalham a experiencia de pagamentos/transferências?

Ainda que haja uma grande cobertura de testes automatizados, para garantir a qualidade de uma aplicação, há casos onde problemas acontecem apenas no ambiente de produção, por diversos motivos, como falhas inesperadas de serviços internos ou internos. Ou seja, mesmo que o CI aponte que todos os testes estão passando, isso não necessariamente vai garantir que não haja problemas em produção.

Especificamente para os dois exemplos acima, as seguintes estratégias poderiam ser tomadas, para diminuir os riscos:

App de Streaming:

Estratégia para casos críticos: Testes manuais, validando a estabilidade da conexão em diferentes redes(devido a complexidade de automatização desse cenário).

Possíveis pontos de ação: Caso haja muita intermitência, o time poderia implementar uma solução de framerate/bitrate adaptativo, que selecionaria qualidade do audio/video, de acordo com a velocidade de conexão do usuário. Diminuindo as chances de o audio/video travar ou bufferizar por muito tempo.

App Financeiro:

Estratégia para casos críticos: Smoke Tests(Unitarios, Integração e/ou Funcionais/UI), para o fluxo de pagamento e transferência, onde um pequeno conjunto dos testes, seria executado, validando que as funcionalidades principais funcionam como esperado e não apresentam bugs.
Em caso de falha, o resto dos testes da aplicação nem precisaria ser executado, como também um release não seria liberado para produção.. até que os Smoke Tests passem. Essa estratégia ajudaria na velocidade do feedback sobre a “saúde” da aplicação. Isso se aplicaria tanto para a aplicação mobile também para o backend.

Possíveis pontos de ação: Rodar os Smoke Tests de forma frequente, no CI/CD, no momento de abertura de PRs, como também no momento de release, para mitigar a possibilidade de falhas em pontos críticos da aplicação. O app deve estar preparado para dar feedbacks claros para os usuários, mesmo em casos de falhas inesperadas.

TL;DR

Vimos que o ato de implementar testes, vai muito além da criação de classes de testes e da escolha de ferramentas.

É necessário envolver o time, decidir o que testar, como testar e qual padrão seguir; é importante pensar na arquitetura, design e em outras particularidades do sistema, para avaliar a melhor forma de testar a aplicação e também precisamos olhar para nossos testes como um auxílio na busca pela qualidade, impedindo que nossos usuários sejam impactados por falhas.

Na parte 2, vamos entender um pouco mais sobre como lidar corretamente com Piramide de Testes e Cobertura de Testes.

Aqui uma lista dos conteúdos que eu consumi até hoje sobre teste, que serviram de referencia para este artigo?

Curtiu? Compartilha o post com quem está num momento de aprendizado/evolução sobre testes e comenta aí, caso você já tenha tido que criar uma estratégia de testes para validar sua aplicação !

--

--

Natan Ximenes

Senior Software Engineer, Android @ League, passionate about Android development, Agile and Digital products.