Entrega contínua com build quebrado e consciência limpa
Por
Publicado: March 30, 2015
Já pensou em fazer entrega contínua no seu projeto? E entrega contínua com o build eventualmente quebrado? Pode parecer perigoso, mas na verdade não é o fim do mundo e é mais comum no dia a dia dos projetos do que você pensa. Concordamos que a entrega contínua deve ser feita com a pipeline de build toda verde, e esse é o nosso objetivo. Este artigo compartilha a experiência de um projeto específico.
Pense em um cenário no qual em torno de 5 builds vão para ambiente de produção todos os dias, mesmo que o build esteja quebrado; no qual fazemos o deploy da produção mesmo que o build de desenvolvimento não esteja todo verde e nem todos os testes estejam passando.
Pense em um projeto de gerenciamento de infraestrutura na nuvem que recebe aproximadamente 10 mil visitas únicas diárias, com 5 times desenvolvendo e empurrando código de dois locais diferentes. Manter o build verde num projeto em que qualquer desenvolvedor trabalha em qualquer parte do código é um grande desafio, porque todos os times têm autonomia para desenvolver e entregar.
Para iniciar os trabalhos, criamos um branch de vida curta (short lived branch) no master e uma flag de funcionalidade que permite aos desenvolvedores continuar empurrando código mesmo que a funcionalidade não esteja completa, uma vez que ela fica escondida pela flag até estar completa - e desenvolvemos a estória. Ao final integramos novamente ao master, rodamos os testes e mandamos ver, empurramos para produção. É importante reforçar que este deve ser um branch de vida curta. Se cada time ou funcionalidade acabar tendo um branch vida longa, isso poderia gerar um problema em si e um verdadeiro inferno de merges.
Para fazer tudo isso acontecer precisamos de uma suite de testes boa e estruturada. Essa suite não é de responsabilidade de um time específico, mas de todos os times envolvidos. Uma estória entregue significa necessariamente testes unitários e de aceitação feitos e integrados. Sendo assim, toda nova funcionalidade está coberta por testes automatizados, garantindo a prevenção de defeitos quando novas funcionalidades são adicionadas.
Um dos nossos times de desenvolvimento nesse projeto era composto apenas por testadores. Estes eram responsáveis por automatizar áreas que não foram cobertas no desenvolvimento das estórias, manter as suites de testes estáveis e aprimorá-las, além de implementar os chamados "caminhos não felizes" e "corner cases".
E quais eram os ambientes em que estávamos trabalhando? Bom, havia quatro tipos:
Todo commit que integramos ao master passa pela suite de teste. Através do fabuloso radiador de integração, os times acompanham o estado da integração. Caso tudo passe, maravilha, vai direto pra produção. Caso alguma suite falhe, investigamos a causa e decidimos sobre fazer a entrega assim mesmo ou não. Se decidimos que sim, o build quebrado vai para produção com a segurança de que se trata ou de um teste/uma suite instável, de uma parte do código que não foi alterada ou simplesmente de que o teste que falhou não era expressivo o bastante para bloquear a entrega.
Como o número de integrações aumentou muito à medida que os times cresciam, rodar a suite inteira toda vez tornou-se insustentável. Por isso, criamos o conceito de "ônibus" de integração. Esse "ônibus" "recolhe" os "passageiros" a cada hora, integra em um pacote único e executa a suite de testes.
Como sabemos, nem tudo são flores, vários são os desafios nessa modalidade de entrega contínua:
A combinação da alternância de funcionalidades com a habilidade de ativar suites de teste correspondentes sob demanda ajudam a garantir que tanto as funcionalidades completas como as incompletas podem ir ao ar sem comprometer a qualidade.
Como você lida com builds quebrados na sua prática de entrega contínua? Contribua com a discussão nos comentários.
Pense em um cenário no qual em torno de 5 builds vão para ambiente de produção todos os dias, mesmo que o build esteja quebrado; no qual fazemos o deploy da produção mesmo que o build de desenvolvimento não esteja todo verde e nem todos os testes estejam passando.
Pense em um projeto de gerenciamento de infraestrutura na nuvem que recebe aproximadamente 10 mil visitas únicas diárias, com 5 times desenvolvendo e empurrando código de dois locais diferentes. Manter o build verde num projeto em que qualquer desenvolvedor trabalha em qualquer parte do código é um grande desafio, porque todos os times têm autonomia para desenvolver e entregar.
Para iniciar os trabalhos, criamos um branch de vida curta (short lived branch) no master e uma flag de funcionalidade que permite aos desenvolvedores continuar empurrando código mesmo que a funcionalidade não esteja completa, uma vez que ela fica escondida pela flag até estar completa - e desenvolvemos a estória. Ao final integramos novamente ao master, rodamos os testes e mandamos ver, empurramos para produção. É importante reforçar que este deve ser um branch de vida curta. Se cada time ou funcionalidade acabar tendo um branch vida longa, isso poderia gerar um problema em si e um verdadeiro inferno de merges.
Para fazer tudo isso acontecer precisamos de uma suite de testes boa e estruturada. Essa suite não é de responsabilidade de um time específico, mas de todos os times envolvidos. Uma estória entregue significa necessariamente testes unitários e de aceitação feitos e integrados. Sendo assim, toda nova funcionalidade está coberta por testes automatizados, garantindo a prevenção de defeitos quando novas funcionalidades são adicionadas.
Um dos nossos times de desenvolvimento nesse projeto era composto apenas por testadores. Estes eram responsáveis por automatizar áreas que não foram cobertas no desenvolvimento das estórias, manter as suites de testes estáveis e aprimorá-las, além de implementar os chamados "caminhos não felizes" e "corner cases".
E quais eram os ambientes em que estávamos trabalhando? Bom, havia quatro tipos:
- Um deles era o staging, um ambiente de adaptação, e tinha o objetivo principal de encontrar erros de integração com APIs externas.
- O ambiente de pre-produção era exatamente igual ao de produção, ali tudo já precisava estar funcionando perfeitamente.
- Lembra das flags de funcionalidade? Pois é, enquanto as estórias estão sendo desenvolvidas, o ambiente "em preview" era onde as histórias que estavam escondidas no ambiente de pré-produção eram testadas.
- Por fim, produção era onde a festa acontecia. Era o ambiente no qual o cliente usava a aplicação.
Todo commit que integramos ao master passa pela suite de teste. Através do fabuloso radiador de integração, os times acompanham o estado da integração. Caso tudo passe, maravilha, vai direto pra produção. Caso alguma suite falhe, investigamos a causa e decidimos sobre fazer a entrega assim mesmo ou não. Se decidimos que sim, o build quebrado vai para produção com a segurança de que se trata ou de um teste/uma suite instável, de uma parte do código que não foi alterada ou simplesmente de que o teste que falhou não era expressivo o bastante para bloquear a entrega.
Como o número de integrações aumentou muito à medida que os times cresciam, rodar a suite inteira toda vez tornou-se insustentável. Por isso, criamos o conceito de "ônibus" de integração. Esse "ônibus" "recolhe" os "passageiros" a cada hora, integra em um pacote único e executa a suite de testes.
Como sabemos, nem tudo são flores, vários são os desafios nessa modalidade de entrega contínua:
- suites muito lentas retardam a entrega;
- testes que falham aleatoriamente são inúteis, pois não são confiáveis;
- dependência de APIs externas causa falhas em testes que não são parte da camada em desenvolvimento;
- a quebra das funcionalidades em produção acontece e precisamos pensar na causa raiz para que o problema não se repita.
A combinação da alternância de funcionalidades com a habilidade de ativar suites de teste correspondentes sob demanda ajudam a garantir que tanto as funcionalidades completas como as incompletas podem ir ao ar sem comprometer a qualidade.
Como você lida com builds quebrados na sua prática de entrega contínua? Contribua com a discussão nos comentários.
Aviso: As afirmações e opiniões expressas neste artigo são de responsabilidade de quem o assina, e não necessariamente refletem as posições da Thoughtworks.