A maioria esmagadora dos desenvolvedores Android que conheço estão interessados ou ativamente estudando novos tópicos relacionados à plataforma. Arquitetura Clean, padrões de projeto aplicados ao Android, Rx e programação reativa, Kotlin, são de fato muitos assuntos e pouco tempo disponível para explorar todos eles.
Apesar do investimento no aprendizado contínuo feito pelos desenvolvedores ser um esforço válido e muito importante, a aplicação e o entendimento corretos destas ferramentas são apenas os primeiros passos para garantir que o aplicativo realmente entregue valor aos usuários. Ainda é necessário que todo código criado seja integrado à base de código principal, compilado em um apk, testado e devidamente publicado em um local onde os usuários possam baixar o aplicativo.
Sendo um desenvolvedor Android, ou de qualquer tecnologia, pare agora e pense: quantas vezes por dia você faz esse fluxo? Quantas vezes você já teve problemas com essas etapas que atrasaram a entrega ou impactaram seus usuários? Quanto tempo leva todo esse processo? Como isso afeta a sua produtividade como desenvolvedor? Quanto tempo levaria para você corrigir um bug em produção?
Se as respostas para estas perguntas lhe trouxeram lembranças dolorosas, ou se o processo de integração de suas mudanças e entrega de uma nova versão são motivos recorrentes de ansiedade em seu dia a dia como desenvolvedor, aqui tratarei de uma prática que pode lhe ajudar nesta batalha.
Integração Contínua para quê?
CI (Continuous Integration), é definida no livro Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation como uma prática que requer que toda vez que um membro do time faça o commit de uma alteração, toda a aplicação deve ser construída e testada por um conjunto de testes automatizados; se a build ou testes falharem o time deve parar e resolver o problema imediatamente.
Essa é uma mudança de comportamento e mentalidade interessante, ao invés de nos fecharmos por dias (em alguns casos semanas) para desenvolver uma feature ou corrigir um bug, acabamos por ter de quebrar essas alterações em pequenas mudanças incrementais integradas e validadas no mínimo diariamente.
Ao aumentar a frequência na qual alterações são integradas e verificadas contra a base de código principal é possível obtermos alguns benefícios como: redução no risco de quebra da aplicação no momento da release para produção, bugs são detectados e corrigidos prematuramente, aumento na velocidade e qualidade das entregas, feedback rápido aos desenvolvedores quanto ao impacto de suas alterações no código.
Entre os benefícios citados, como desenvolvedor, acredito que Feedback Rápido seja o mais interessante, pois ajuda a eliminar aquele medo perturbador do impacto de sua alteração no código e dá segurança para trabalhar em refactorings, em módulos e partes desconhecidas. Certa vez em um projeto, o time do qual eu fazia parte recebeu o relato de um bug que, após análise, descobrimos que havia sido causado por uma alteração realizada 6 meses antes e só foi descoberto pois era uma funcionalidade usada apenas por clientes específicos em datas específicas, que tipo de ansiedade essa situação gera no time em mudanças posteriores?
Adotando CI
Apesar de parecer uma prática simples, sua implementação requer disciplina e atenção, pois boa parte do trabalho consiste na colaboração dos membros do time em adotar as atividades propostas pela integração contínua.
Antes de Começar
Pressupõe-se que o time já tenha se munido de algumas coisas importantes.
Um Sistema de Controle de Versionamento
Para poder integrar suas alterações à base de código principal é necessário ter o código principal disponibilizado em uma ferramenta que não somente possibilite a integração e o controle mas que seja flexível e facilite o processo.
Uma combinação de Git com Github, Gitlab, Bitbucket ou similares é um ótimo começo a um custo relativamente baixo.
Processo de Build Automatizado
Esta etapa é a que a maioria dos desenvolvedores acaba dando maior foco, já que entrega algum ganho de produtividade logo após sua implementação. No entanto, boa parte esquece que além da construção propriamente dita é fundamental que se configure etapas onde os testes automatizados são executados, ferramentas de análise estática de código gerem seus relatórios, e até mesmo o aplicativo final seja disponibilizado para quem for de interesse.
A automatização do processo é o que ajuda a garantir o Feedback Rápido já mencionado anteriormente, sem ela é muito provável que se perca confiabilidade e que os membros do time aos poucos abandonem a prática de CI.
Para Android, geralmente a build é automatizada com uso do Gradle em conjunto com alguma ferramenta específica para CI (listo algumas delas logo adiante).
Práticas de CI
Com os pré-requisitos já configurados e prontos para serem utilizados, a integração contínua foca em práticas a serem adotadas pelo time de desenvolvimento. Mais uma vez, o livro Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation traz um conjunto dessas práticas que podem ser avaliadas e adotadas pelos times de desenvolvimento, são elas:
- Faça check in regularmente
- Crie um conjunto de testes automatizados
- Mantenha o processo de testes e build curtos e rápidos
- Faça uma gestão adequada do seu ambiente de desenvolvimento
- Não faça check in de uma build quebrada
- Sempre rode os testes localmente ou no CI antes de integrar as alterações
- Espere o resultado dos testes antes de iniciar uma nova atividade
- Nunca vá para casa com uma build quebrada
- Sempre esteja pronto para reverter suas alterações
- Tente corrigir por um tempo determinado antes de reverter
- Não comente/desative testes que estão falhando
- Assuma a responsabilidade por quebras causadas por suas mudanças
- Pratique Test Driven Development (se possível)
Além das práticas acima, consideradas essenciais, o mesmo livro sugere um conjunto de práticas complementares que em geral se referem a verificação da qualidade do código, aderência a padrões de desenvolvimento do time e outras métricas de interesse do próprio time.
Um ponto importante a se verificar é que essas práticas não dependem diretamente de ferramentas, mas da disciplina dos membros do time em executar essas atividades em seu dia a dia. Por isso é muito importante que todos estejam focados em adotar essas práticas e colaborar com a melhoria do processo de desenvolvimento.
Dicas e Cuidados de CI para o Robozinho Verde
Todas as práticas voltadas à integração contínua são aplicáveis ao universo Android, no entanto existem alguns pontos que podem ser avaliados especificamente para esta plataforma e que podem ajudar na adoção do CI.
Ambiente, API Level e Build Tools
Todo sabemos o quão chato pode ser a configuração de ambiente para Android: baixar gigabytes de ferramentas, acertar a versão correta das ferramentas de build e diversos pequenos detalhes que se tornam tão automáticos que acabamos esquecendo de sua importância.
Como geralmente a build automatizada irá rodar em uma máquina limpa, é muito importante automatizar este processo de configuração. Se você adotar uma ferramenta SaaS procure uma que possibilite a configuração automática do ambiente. Caso prepare um ambiente por conta própria, tente usar Docker ou scripts que automatizem esta configuração.
Lint
Quem nunca teve uma build de release interrompida por um warning do Lint que atire a primeira pedra. O Lint pode ser a primeira linha de defesa contra bugs que podem chegar a seus usuários.
Configure o Lint para que rode em suas builds, monitore a evolução da quantidade de warnings ou crie regras para que a build seja interrompida sempre que esse número cresça e o time foque em analisar e corrigir potenciais problemas encontrados pela ferramenta.
Dependências e Velocidade das Builds
Com a facilidade proporcionada pelo Gradle para incluir bibliotecas e utilitários em nossos projetos muitas vezes não prestamos atenção em como isso pode impactar no tempo de build de nossas aplicações.
Se sua build na ferramenta de CI estiver levando muito tempo, lembre-se de verificar quanto tempo o processo está levando para baixar dependências. Como a build na ferramenta de CI pode estar rodando em uma máquina limpa, procure ferramentas que possibilitem o cache de dependências para acelerar o processo.
Também fique de olho em dependências que incluem ferramentas de pré-processamento como o Dagger2 ou mesmo o Kotlin que também podem adicionar tempo ao seu processo de build.
Uma outra causa para o aumento do tempo de build é o crescimento natural da aplicação. Nestes casos considere a modularização por funcionalidades e crie processos de CI separados para cada módulo e para a aplicação principal.
Rode seus Testes
Sempre busque ferramentas de CI que possibilitem que seus testes rodem de forma automática. Para todos os commits que dispararem uma build na ferramenta de CI rode pelo menos os testes unitários (./gradlew testReleaseUnitTest), para commits que são integrados ao branch principal rode testes unitários, de integração e todas as verificações configuradas (./gradlew check).
Lembrem-se de garantir que todos os desenvolvedores saibam como rodar os testes automatizados em suas máquinas, crie guias e páginas wiki, organize seus testes em Test Suites de forma que os desenvolvedores possam rapidamente rodar testes relacionados a um módulo no qual estejam trabalhando.
Testes instrumentados levam muito tempo para serem executados e exigem um emulador ou device externo. Sempre que possível foque-os em testes de aceitação e monitore quantidade e tempo de execução para evitar um crescimento descontrolado do tempo de build. Esse tipo de teste pode ser rodado localmente com o comando ./gradlew connectedAndroidTest. Para ter diversidade de dispositivos para testes por um custo relativamente acessível procure ferramentas de CI que possibilitem a integração com clouds de dispositivos para testes como Firebase Test Lab, Xamarin Test Cloud, AWS Device Farm, Bitbar Testing, Kobiton, Perfecto Mobile, Sauce Labs Mobile Testing, Experitest Mobile Cloud Testing ou crie seu próprio farm com o Open STF.
Automatize o Release e Utilize Ferramentas de Beta
Ter a aplicação sempre disponível e pronta para deploy é realmente algo excelente. No entanto, no universo mobile não é interessante fazer seu usuário baixar atualizações múltiplas vezes por dia. A frequência de releases de um app Android pode afetar a percepção dos usuários sobre o app, se for muito frequente a aplicação pode parecer cheia de problemas e se pouco frequente, abandonada. Faça testes e encontre o equilíbrio para o seu público. De qualquer maneira, é importante escolher ferramentas que possibilitem o deploy diretamente para Play Store quando desejado.
Releases frequentes podem ser especialmente importantes para distribuir o aplicativo para públicos internos ou selecionados e coletar feedbacks o mais rápido possível. Algumas opções incluem o Crashlytics Beta, Hockey App, Buddy Build, Fastlane, TestFairy.
Ferramentas de CI com Suporte ao Android
Existem diversas ferramentas que podem ser utilizadas para criação de uma estrutura CI para um projeto Android. Abaixo segue uma lista não definitiva e não priorizada de algumas delas:
Um Caso Atual
Recentemente participei de um projeto no qual, ao conversar com o time e áreas relacionadas, percebi uma oportunidade de melhoria do processo de integração contínua. A aplicação Android já era desenvolvida a aproximadamente 4 anos, o time de desenvolvimento era bem novo (os desenvolvedores Android tinham menos de 1 ano de projeto), o sistema de controle de versionamento utilizado era o Git em combinação com o Github e a ferramenta de CI que estava sendo utilizada era o TeamCity.
Apesar de possuir uma ferramenta de CI que automatizava o processo de build, poucas das outras práticas de CI foram implementadas e algumas reclamações eram muito comuns: medo de refatorar o código e integrar novas alterações, falta de visibilidade de quais solicitações faziam parte de cada release, ocorrência de bugs alta, bugs corrigidos que ressurgiam, demora na liberação de novas versões finais e intermediárias, difícil saber qual o estado atual da aplicação e dificuldade em manter a ferramenta de CI.
Inicialmente resolvemos atuar na melhoria da utilização do Git. O projeto não possuía um fluxo bem definido de utilização, o que impactava diretamente no entendimento do time do que estava sendo integrado e na eficiência dessas integrações. Adotamos como referência o Git Flow que nos permitiu organizar e ter uma visão clara do que estava em produção, desenvolvimento, releases candidatas etc. Uma parte importante desta etapa foi o trabalho conjunto com os desenvolvedores para deixá-los mais confortáveis no uso do Git, facilitando a continuidade do uso do modelo escolhido.
O passo seguinte foi atuar na ferramenta de integração contínua. A ferramenta em uso era o TeamCity que, apesar de madura, no setup que havia sido feito trazia algumas dificuldades: configuração manual do ambiente Android em máquina Windows, mistura de scripts de build em powershell e gradle, disparo da build manual, complexidade de configuração e manutenção da ferramenta para um time com poucos recursos. Optou-se pela migração para uma ferramenta SaaS sem necessidade de gestão e que fosse produtiva na configuração da build para Android, a ferramenta escolhida foi o Bitrise.io que possibilitou: a configuração completa em poucas horas, configuração de cache de dependências, integrações com Slack, Github (para tagging), Crashlytics Beta, publicação de apks em repositório próprio, o disparo de builds automático de acordo com as branches que recebem alterações no github, além da execução de testes unitários.
A terceira etapa, que está em andamento no momento, é a adoção das práticas de CI pelo time que, mesmo com comprometimento, leva um pouco mais de tempo e disciplina para que todo o processo se torne um hábito. Além disso, melhorar a cobertura da suite de testes para que o time tenha mais confiança ao submeter alterações tem sido um grande desafio, já que a cobertura atual é baixa e a arquitetura em uso no momento não colabora com este processo.
Para o futuro a ideia é aumentar a cobertura dos testes unitários e criar suites específicas para testes de aceitação, estudar formas de viabilizar os testes instrumentados em conjunto com o Bitrise.io e incluir ferramentas de análise estática de código como o FindBugs e o CheckStyle.
Conclusão
CI não é algo novo ou revolucionário, Kent Beck já falava dessa prática em seu livro Extreme Programming Explained de 1999. Seu conjunto de técnicas e sugestões simples continuam sendo atuais e podem ser aplicados para beneficiar seu dia a dia como desenvolvedor trazendo ganhos de produtividade e de qualidade nas entregas.
Portanto, se você tem perdido o sono, e talvez os cabelos, toda vez que precisa integrar mudanças ou liberar uma nova versão de sua aplicação, considere aplicar práticas de CI no seu dia a dia. A maioria delas depende apenas do comprometimento do time, com uma boa conversa e dedicação em pouco tempo é possível perceber a evolução na produtividade e qualidade das entregas, além do retorno de uma boa dose de tranquilidade à suas atividades.
Você tem adotado integração contínua em seus projetos Android? Como tem sido a experiência?
Links e Referências
- Continuous Integration by Martin Fowler
- Continuous Integration by ContinuousDelivery.com
- CI Links by Paul Hammant
- Large-Scale Continuous Testing in the Cloud by John Penix from Google
- Continuous Integration: Improving Software Quality and Reducing Risk
- Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation
- Why Continuous Integration is so Important by Walmyr Filho