Na DoorDash, o telemóvel é uma parte integrante da nossa experiência de utilizador final. Os consumidores, os Dashers e os comerciantes confiam nas nossas aplicações móveis todos os dias para a entrega. E a nossa equipa Android move-se rapidamente para enviar funcionalidades impactantes que melhoram a experiência do utilizador e a eficiência da nossa plataforma.
No passado, verificávamos a funcionalidade dos nossos lançamentos executando-os manualmente através de um conjunto de testes de regressão. De modo a escalar o processo de desenvolvimento e lançamento com a nossa equipa em crescimento, tentámos automatizar o máximo possível do processo de teste. Tomámos duas medidas importantes para o conseguir:
- Adotar um padrão de conceção testável
- Automatizar os testes de integração de ponta a ponta para substituir os testes manuais antes dos lançamentos
Para tornar o código testável, é importante dissociar a lógica da aplicação dos componentes Android. O Model View Presenter (MVP) e o Model View View Model (MVVM) são os dois padrões de design mais populares utilizados nas aplicações Android. O MVP desacopla a lógica da visualização e facilita a escrita de testes para essa lógica. O MVVM utiliza a ligação de dados para o conseguir. Escolhemos o MVP porque era fácil de aprender e integrava-se muito bem com o resto da arquitetura das nossas aplicações. Conseguimos atingir quase 100% de cobertura de teste da lógica da visão e da aplicação usando esse padrão.
Let’s take a look at the login functionality in our app using MVP:
https://gist.github.com/de1b669075d6cdd46fe68890a9948244Esta arquitetura dá-nos uma separação clara entre a vista, o negócio e a lógica da aplicação, o que torna muito fácil escrever testes. O código é dividido nos seguintes componentes:
- LoginContract.java define o contrato entre a vista e o apresentador, definindo as duas interfaces.
- LoginPresenter.java trata das interacções dos utilizadores e contém lógica para atualizar a IU com base nos dados recebidos do gestor.
- LoginActivity.java apresenta a IU de acordo com as instruções do apresentador.
- AuthenticationManager.java faz a chamada da API para buscar o token de autenticação e o salva em SharedPreferences. Essa classe nos dá a capacidade de desacoplar a lógica do aplicativo do MVP. Outras responsabilidades das classes de gerenciador que não são abordadas neste exemplo incluem o armazenamento em cache e a transformação de dados do modelo.
Muitas vezes, quando se implementa uma nova arquitetura, a tendência é reescrever toda a aplicação a partir do zero. Numa startup em rápida evolução como a nossa, esta abordagem não é viável, uma vez que precisamos dos nossos recursos de engenharia para trabalhar em melhorias de produtos no nosso negócio em evolução. Em vez disso, quando fazemos grandes mudanças em áreas da base de código, aproveitamos a oportunidade para refatorar o MVP e adicionar testes unitários.
Automatizar os testes de integração de ponta a ponta para substituir os testes manuais antes dos lançamentos
MVP with unit tests alone isn’t enough to guarantee stable releases. Integration testing helps us to make sure our apps work with our backend. To accomplish this, we write end to end integration tests for critical flows in our apps. They are black-box tests that verify whether our apps are compatible with our backend APIs. A simple example is the login test. In this test, we launch the LoginActivity, enter the email and password, and verify that the user can login successfully:
https://gist.github.com/ee6182e9db1c94ec8e29e0d441fa15e2Utilizamos o Espresso para escrever este teste. Como estamos a fazer uma chamada real à API, temos de esperar que esta termine antes de passar à operação de teste seguinte. O Espresso fornece o IdlingResource para nos ajudar nesta tarefa. Tal como definido no método isIdleNow, LoginIdlingResource diz ao Espresso para esperar até que a chamada de início de sessão termine e a aplicação passe para DashboardActivity.
Tomámos as seguintes medidas para criar a infraestrutura para executar testes de aceitação:
- Configurou uma base de dados com dados de amostra e um servidor Web local para servir pedidos de API numa máquina no nosso escritório
- Direcionar este servidor web em nossos testes. Para nós, isso envolveu a criação de uma nova variante de compilação com um URL de base diferente para o Retrofit
- Configure a Integração Contínua com o Jenkins para executar esses testes. Usamos a mesma máquina para hospedar o servidor Jenkins CI que executa esses testes sempre que o código é mesclado em nosso ramo de desenvolvimento principal
- Webhook do Github configurado para que o Jenkins informe o status da compilação para cada pull request
We write and maintain acceptance tests for critical flows in our apps. Continuous integration using Jenkins has helped us catch bugs very early in the release cycle. It has also helped us scale our development and testing efforts as we don’t need a lot of manual testing before our releases.
Conclusão
Estas estratégias de teste ajudaram-nos imenso a tornar os nossos lançamentos mais suaves e mais fiáveis. Embora a estratégia de teste de aceitação automatizada descrita neste artigo tenha algum custo de configuração inicial, ela é muito bem dimensionada depois que a infraestrutura é construída. Além disso, o MVP garante que os novos recursos sejam testados nos níveis unitário e funcional. Esperamos que estes passos o ajudem a reduzir a quantidade de testes manuais e a ganhar mais confiança nos seus lançamentos.
Alguns recursos que ajudaram ao longo do caminho
- Configuração do MVP: http://engineering.remind.com/android-code-that-scales/
- Configuração de variantes de compilação: https://developer.android.com/studio/build/build-variants.html#build-types
- Configurar um emulador para visar um servidor Web local: https://medium.com/@crysfel/updating-the-hosts-file-on-android-emulator-d61a533a76cf
- Configurar a integração contínua usando Jenkins: https://github.com/codepath/android_guides/wiki/Building-Gradle-Projects-with-Jenkins-CI#create-the-build-job