O que é Entrega Contínua
Considere o seguinte questionamento considerando o processo de desenvolvimento atual da equipe em que você trabalha:
Se algum cliente ou o gerente de produto trouxer uma nova ideia, como garantir que a entrega em produção seja feita o mais rápido possível garantiando a qualidade na entrega?
Em um produto já estabelecido, com múltiplos times cuidando de diversas soluções, é necessário ter preocupação com a capacidade de entrega em produção com agilidade. O mercado de produtos de tecnologia está cada vez mais aquecido, demandando que novas ideias sejam implantadas rapidamente. Além disso, problemas em produção surgem e devem ter suas correções entregues independente do estado em que o código se encontre no repositório compartilhado.
Sendo assim, para uma entrega “às pressas” podem haver pontos de atenção como os listados abaixo:
A Entrega Contínua te ajuda a resolver esses problemas e nesse artigo falaremos sobre os príncipios e práticas que, se bem utilizados, te ajudará a implantar Entrega Contínua com sucesso.
Entrega Contínua é um modelo de entrega de software (software delivery) em que novas funcionalidades e soluções de bugs são entregues em produção rapidamente de forma segura e sem impacto negativo para os usuários.
Adicionalmente, tem como um dos objetivo resolver problemas relacionados a mudança de estratégia e prioridades, diminuíndo o impacto dessas mudanças no fluxo de trabalho do time uma vez que as alterações de código são continuamente entregues em produção.
Vale lembrar que o entendimento de entrega em produção é o termo usado para definir as etapas entre o commit/push dos desenvolvedores até as alterações serem percebidas pelos usuários, ou seja, ao menos um usuário estará habilitado a usar a nova funcionalidade.
No dia-a-dia, é comum relacionar a não adoção de Entrega Contínua por motivos de demandas muitos grandes que envolvem muitas alterações de código ou vários commits intermediários, ou ainda ser uma demanda complexa que demoraria algumas semanas para estar pronta. No entanto, a Entrega Contínua é exatamente o que simplifica o processo até mesmo nesses cenários.
Ainda, a adoção correta da Entrega Contínua depende de mudanças culturais. Para a implentação com sucesso, é necessário seguir algumas práticas e metodologias que você verá a seguir.
Princípios e práticas
Alguns princípios são essenciais para a adoção da Entrega Contínua. São eles:
Muito se fala sobre ferramentas e plataformas tidas como “necessárias” e “habilitadoras” da Entrega Contínua. Na maioria dos casos, com ferramentas simples é possível – e quase sempre mais fácil – implemetar com sucesso Entrega Contínua
Integração Contínua
A integração contínua (CI) é uma das práticas mais importantes da entrega contínua. A integração contínua automatiza o processo repetitivo de integração de código novo em um repositório de código compartilhado. Isso ajuda a garantir que o código novo seja integrado com o código existente continuamente.
A integração contínua se diferencia da entrega contínua pois esta última define fases além da integração de código, garantindo que o código seja devidamente empacotado, testado e entregue em produção.
![Entrega Contínua (Delivery Contínuo): princípios e práticas](http://thiagofurtado.com/wp-content/uploads/2023/09/Entrega-Continua-Cover-1024x536.jpg)
Considero a integração contínua como sendo o primeiro grande desafio a ser vencido. Ora, a entrega começa na ação de commit/push dos desenvolvedores. Há várias estratégias usadas para implementar a integração contínua: branch única, gitflow, branch local, entre outras. Destaco aqui a estratédia de branch única.
Manter uma branch única simplifica o uso do controle de versão. Além disso, reforça a responsabilidade de commits pequenos que levam os desenvolvedores a pensar em alterações em pequenos lotes.
Feature Toggle
Manter o repositório de código atualizado com commits diários é o cenário perfeito para integração contínua. Porém, é necessário controlar a entrega de funcionalidades experimentais e inacabadas. Feature Toggles é o caminho para tal controle.
Em um ambiente tradicional, os desenvolvedores precisam esperar até que uma funcionalidade esteja totalmente desenvolvida e testada antes de poder liberá-la em produção. Isso pode levar meses.
Os feature toggles são uma técnica que permite aos desenvolvedores liberar recursos inacabados ou experimentais sem afetar a experiência do usuário. Um feature toggle é, em geral, uma variável de configuração que pode ser usada para habilitar ou desabilitar uma funcionalidade ou um fluxo de código em tempo de execução.
Logo, os desenvolvedores podem entregar em produção um código que será usando condicionalmente. Com isso, é possível liberar uma nova funcionalidade para um ambiente específico, um grupo de usuários específicos, ou até mesmo para usuários que dêm opt-in para funcionalidades experimentais.
Os feature toggles são uma ferramenta importante para a entrega contínua, pois permitem que os desenvolvedores:
É importante o uso de feature toggles com disciplina. Usar feature toggles para qualquer fluxo, sem uma definição de propósito pode levar a códigos complexos e sujos. Abaixo, uma lista de dicas para uso de feature toggles
Os feature toggles são uma ferramenta essencial para a entrega contínua. Ao usá-los de forma eficaz, os times de desenvolvimento podem liberar novos recursos, recursos inacabados e experimentos de forma mais rápida, segura e confiável.
Lotes Pequenos (Small Batches)
Um dos principais desafios da entrega contínua é a mudança cultural. Os times de desenvolvimento precisam mudar de uma mentalidade de “lançar o produto perfeito” para uma mentalidade de “lançar o produto o mais rápido possível e melhorar com o tempo”.
Com a integração contínua e os Feature Toggles já entende-se que é possível ter código integrado todo o tempo até mesmo para códigos que ainda não estão de fato lançados. Porém, para evitar commits com muitas alterações, ou mescla de código com quantidade elevada de arquivos modificados, é necessário ‘quebrar’ as demandas em pequenas entregas.
O conceito de “small batches” pode ajudar os times de desenvolvimento a superar esse desafio. Este conceito baseia-se na ideia de que é possível dividir grandes demandas em demandas menores, que em tese, seriam mais simples de gerenciar. Assim, ao adotar este conceito os times podem criar funcionalidades complexas de maneira incremental.
É importante começar com pequenas entregas. Em vários casos, a primeira entrega de uma funcionalidade complexa é o feature toggle. Como exemplo, a entrega de um dashboard pode ser dividida entre uma nova página e uma entrega para cada gráfico exigido. Com isso, tem-se:
Parece simples, mas não é. Principalmente para times novos ou produtos em estágios iniciais, momentos de “viagem mental” é natural e acontece com frequência; de reuniões para definir solução para um problema saem mais dois ou três problemas. E é tentador tentar resolver tudo numa história de usuário só. A sugestão aqui é que comece por dividir uma demanda em tarefas. Realizar uma cerimônia de quebra de história de usuário é uma forma simples e eficaz de fazer essa divisão.
Além disso é importante que as tarefas menores sejam priorizadas. Em ambientes tradicionais de desenvolvimento em que funcionalidades não são divididas em pequenos lotes, é comum que as funcionalidades apresentem gorduras por desejos e anseios. Ao dividir tarefas complexas em lotes pequenos, fica fácil distinguir o que é útil e necessário e o que pode ser deixado para depois dos feedbacks.
Por fim, comece com o que é mais importante. Foque nos recursos e funcionalidades mais importantes para os usuários. E então, melhore continuamente, em pequenos passos metrificáveis.
Automação de tarefas repetitivas (pipeline de entrega)
Tarefas repetitivas consomem tempo, e nesse caso o tempo é multiplicado pela quantidade de vezes que essas tarefas são realizadas. Além do mais, é sustetível a erro executar tarefas sequênciais dentro de ambientes que sofrem alterações; são vários os momentos em que a sequência/processo precisa ser atualizada.
Esse cenário é compatível com entrega de software. Afinal, as várias etapas para entregar um software em produção em geral são etapas identicas, repetidamente realizadas pelos integrantes do time. O problema é o fator humano: em algum momento, a etapa de executar os testes é esquecida.
Entrega contínua define que haja rapidez e confiabilidade. Logo, as etapas de testes unitários, testes de aceitação, validação de disponibilidade de serviços de terceiros, se mantidas manuais, geram erros. A solução do preguiçoso é dizer que documentará o processo, que em algum momento estará desatualizado.
Lembra-se do lema “O teste é a melhor documentação do código”? Grave mais um lema: O pipeline de entrega é a melhor documentação do processo de entrega.
Embora o artigo seja sobre Entrega de Software, há outros aspectos além da entrega que podem ser automatizados: alertas, atualizações de bando de dados, envios de relatórios, entre outros. A automatização de tarefas repetitivas promove:
Adicionalemnte, é importante que desenvolvedores busquem conhecimento em shell script, ferramentas de gestão de pipelines como Jenkins, Gitlab CI ou Bitrise. Atentem-se em começar o pipeline de entrega pequeno, com um script simples e então ir melhorando-o continuamente. Ao implantar inicialmente uma automação complexa, demandará tempo dando manutenção em algo não necessário.
Teste e Qualidade de Software
“Se eu ‘commitar’ vai pra produção”? Quem nunca escutou essa pergunta trabalhando em um time de desenvolvimento? E a principal razão da cautela percebida nessa questão é culpa da Qualidade de Software (ou da falta dela).
Um dos princípios centrais de qualidade de software em ambientes de entrega contínua é a automação dos testes. Cenários de testes se repetem e a cada nova funcionalidade só aumentam. Considerando a seção anterior acredito que já tenha refletido que testes precisam ser sistematicamente realizados.
Há diferentes abordagens e níveis de testes que juntos podem ser usados para garantir qualidade de software. Neste artigo tratamos sobre Testes Unitários, Testes de Integração e Testes de Aceitação.
Testes Unitários
Tradicionamente, testes unitários são definidos como a prática de testar cada pequena parte única do código: funções, métodos, classes, módulos, etc., garantindo assim que, se cada parte funciona corretamente, então o todo também funciona. No entando, nem sempre isso é verdade.
Ora, a entrega contínua trata de entrega de funcionalidades em um software. Sendo assim, é possível definir teste unitário no nível da unidade de cada funcionalidade. Por exemplo, numa funcionalidade onde a entrega é uma nova URL em uma API que forneça a lista dos seguidores de um usuário e, ainda considerando que todo código escrito é código utilizado (sem códigos legados deixados por apego), é possível escrever cenários de testes que validem todos as entradas (incluindo entradas com erros e casos extremos) e todas as saídas possíveis (incluindo exceções e resultados de erros) escrevendo apenas testes que acessam apenas a URL.
O teste unitário deve então executar fluxos completos e não apenas testar funções ou classes. Dessa forma evita-se o uso de mocks de código pertencente ao código. Nunca se deve ‘mockar’ um código que não seja de terceiro.
Outrossim, não se deixe ser levado pela vaidade de ter 100% de cobertura nos testes. Certifique-se de que testou 100% dos cenários possíveis partindo do ponto de entrada da funcionalidade, criando mocks apenas de bancos de dados e API’s de terceiros; afinal, alguém já testou o banco de dados que estás usando.
Testes de Integração
Testes de integração vão um passo além dos testes unitários. Logo, o que antes era ‘mockado’ ou simulado, agora precisa ser de fato utilizado.
Em geral usa-se ambientes de homologação com a mesma versão que irá para produção para executar tais testes. Nesse caso, uma inserção em banco de dados é feita de fato no banco de dados de homologação e os testes devem validar os dados inseridos. Conexões com outros módulos ou microserviços também são testados nesse nível de testes.
Testes de Aceitação
O teste de aceitação é aquele realizado na perspectiva do usuário.
O que o teu usuário de fato vê e usa, uma tela num aplicativo iOS, Android ou website? Fazer essa pergunta irá te ajudar a definir o que deve ser testado no nível de teste de aceitação.
Testes de aceitação tem características mais robustas e em geral levam mais tempo para ser executados. Prefira nesse caso testar apenas as funcionalidades mais importantes que impediria os usuários de usar. Por exemplo.: uma tela de área administrativa, se não funcionar corretamente não inviabiliza o uso dos usuários comuns e pode ser evitada no nível de testes de aceitação.
Monitoramento
No mundo ideial, uma aplicação bem testada não apresentaria erros em tempo de execução. Porém, todo software tem ao menos um bug.
O monitoramento de aplicações é imprescindível levando em consideração que bugs em produção levarão a incidentes, que impactam a experiência do usuário. Seja por uma informação errada ou uma queda de desempenho, impactos negativos na experiência de usuário podem trazer insatisfação. Isso claramente irá refletir na reputação em lojas de aplicativos, por exemplo.
Com um bom monitoramento, é possível entender em tempo real o comportamento das aplicações, diminuir o tempo para identificação de problemas ou até mesmo detectá-los de maneira precoce, promover a melhoria contínua da qualidade, reduzir o tempo total de indisponibilidade e ainda auxiliar na tomada de decisões de negócios com o feedback rápido que o monitoramento permite.
Acontece que, monitorar corretamente um software não é uma tarefa simples. A visibilidade é limitada em diversos casos: aplicações modulares de backend e aplicativos embarcados dificultam a análise em tempo real dada a descentralização das ocorrências de erros.
Para implementar um bom monitoramento em aplicações sempre tenha em mente três pontos de atenção:
Logs e Tratamento de Exceções
É incrível como algo como testes e logs de aplicação sejam tão triviais para engenheiros de software e ao mesmo tempo seja negligenciado pela maior parte deles.
Da mesma forma como os testes, tenha em mente que a definição da estratégia de monitoramento faz parte da funcionalidade sendo desenvolvida. Em tempo de definição técnica de uma demanda é importante definir o que será monitorado e quais alertas precisam ser definidos.
Outro ponto importante e que muitas vezes por pressa ou não é deixado de lado, é o tratamento correto de exceções. Dica: utilize os cenários de testes definidos para guiar como escrever cada exceção em log.
Em resumo, nunca se esqueça:
Conclusão
A Entrega Contínua é muito mais do que apenas uma metodologia de desenvolvimento de software; é uma cultura que revoluciona a maneira como entregamos valor aos nossos clientes e como gerenciamos nossos projetos. Este artigo explorou os princípios e práticas essenciais da Entrega Contínua, desde a Integração Contínua até o monitoramento e tratamento de exceções.
A principal lição que podemos tirar é que a Entrega Contínua não é apenas sobre entregar mais rápido, mas também sobre entregar com qualidade e confiabilidade. Ela nos ensina a quebrar grandes demandas em pequenos lotes, automatizar tarefas repetitivas e garantir a qualidade do nosso software por meio de testes abrangentes.
Além disso, a Entrega Contínua nos lembra da importância do monitoramento, do tratamento adequado de exceções e da criação de alertas relevantes. Isso não apenas nos ajuda a identificar problemas precocemente, mas também a melhorar constantemente nossos processos e produtos.
Em resumo, a Entrega Contínua é uma abordagem holística para o desenvolvimento de software que nos permite entregar valor de forma rápida, confiável e com qualidade. Ao adotar seus princípios e práticas, as equipes de desenvolvimento podem enfrentar os desafios do mercado de tecnologia em constante evolução e fornecer soluções que atendam às demandas dos clientes de forma eficaz. Portanto, não apenas acelere a entrega de software, adote a Entrega Contínua e eleve a qualidade e a eficiência de seus projetos de desenvolvimento.