Segundo o Martin Fowler a Integração Contínua (CI) é uma prática de desenvolvimento de software onde os membros de uma equipa integram seu trabalho de forma frequente, geralmente cada elemento da equipa faz essa integração pelo menos uma vez por dia. Aqui cada integração é verificada por uma compilação automatizada (incluindo um conjunto de teste) para detectar erros de integração tão rapidamente quanto possível. Esta abordagem leva a que o tempo que uma equipa demora a encontrar problemas de integração seja reduzidos significativamente levando a um desenvolvimento de software mais coeso.
Segundo o mesmo Martin Fowler existe um conjunto de princípios básicos que se devem seguir.
Practices of Continuous Integration
- Maintain a Single Source Repository.
- Automate the Build
- Make Your Build Self-Testing
- Everyone Commits To the Mainline Every Day
- Every Commit Should Build the Mainline on an Integration Machine
- Keep the Build Fast
- Test in a Clone of the Production Environment
- Make it Easy for Anyone to Get the Latest Executable
- Everyone can see what's happening
- Automate Deployment
Assim o objectivo é criar um Sistema de CI de modo a que os erros cometidos durante o desenvolvimento sejam o mais rapidamente detectados . Assim olhando para as práticas definidas pelo Martin Fowler vamos tentar cumprir todas elas. Comecemos a analisar as mesmas e a verificar como as podemos cumprir.
Maintain a Single Source Repository
Deve existir um branch de desenvolvimento único por projeto em que toda a equipa deverá trabalhar. Outros branchs apenas para correções de bugs em versões de produção antigas. Para o desenvolvimento de novas funcionalidades usar um mecanismo que permita ligar e desligar essas mesmas funcionalidades, para que estas não sejam visíveis nas versões de produção. O Martin Fowler criou um post no seu blog onde dá um conjunto de dicas bastante úteis sobre como gerir a problemáticas dos branchs. Este post pode ser consultado aqui.
Estando a falar de repositórios deixo algumas notas sobre o que deve ser feito antes de efetuar um commit para o repositório, e que cuidados se devem ter em conta:
- Deve atualizar a sua cópia local para obter todas as novas alterações feitas por terceiros desde a última atualização do repositório.
- Se existirem conflitos, estes devem ser resolvidos localmente. Não se deve simplesmente sobrepor as alterações de terceiros.
- Deve compilar o código antes de efetuar um commit. Se não compilar, deve ser corrigido e só depois colocado no repositório.
- Se o código compilar sem problemas, deve executar um conjunto básico de teste. Pelo menos executar os testes rápidos, todos eles e não apenas os que você se acabou de escrever ou modificar.
- Se um dos testes falhar, deve corrigir o código que deu origem ao erro, ou por outro lado, corrigir o teste se este já não for o mais correto. Repita o processo até que nenhum dos testes falhe.
- Por fim deve-se efetuar o commit do código.
Automate the Build
Este ponto pode ser visto de dois lados, do ponto de vista do desenvolvimento e do ponto de vista do CI. Para um novo programador que se junte a uma equipa o período de tempo entre um checkout e o ter a aplicação em estado de criação de novos desenvolvimento deve ser mínimo (Entre 10-15m, a partir daí já começa a ser tempo perdido em demasia). Claro que destas estimativa se exclui-se deste tempo as instalações mais básicas como SO, IDE de desenvolvimento e ambientes Java, .Net, Ruby. Em suma, o processos de compilação deve permitir que qualquer pessoa seja capaz de trazer uma máquina virgem, retirar as fontes do repositório, correr um único comando, e ter o sistema pronto a correr na sua máquina.
Por outro lado devemos automatizar o processo de compilação numa máquina dedicada ao CI. Para se chegar a este objectivo existem alguns utilitários como o Ant, o Maven ou mesmo o MSBuild. Todas elas têm os seus pontos fortes e fracos. O MsBuild é muito bom para automatizar a compilação do código dos projetos .Net. È só escolher a mais adequada e começar a automatizar todo o processo de build.
Claro que nem sempre tudo corre bem e as vezes existem falhas nas builds. Então o que devemos fazer quando estas falham?
Primeiro é necessário identificar o que falhou. Aqui existem normalmente vários candidatos possíveis desde erros no código, a testes que não passaram, erros que não passaram ou até mesmo uma simples falta de espaço na máquina de CI, assim:
- A primeira coisa a fazer é identificar qual é o problema.
- Se o problema for derivado a um erro de compilação, deve-se imediatamente alertar a equipe de desenvolvimento (para que estes não atualizem a sua cópia local com o código partido), e posteriormente descobrir quem é o responsável. Verifique que este erro é corrigido o mais rapidamente possível.
- Se o problema for derivado a testes que falharam verificar se o teste ainda se encontra adequado à situação a verificar. Deve-se adequar o mesmo caso esta situação já não se verifique. Se por outro lado o problema estiver no código que está a ser validado por um determinado teste verificar quem é o responsável por essa parte do código e com ela verificar o problema.
Uma build partida deve ser corrigida de forma rápida, porque geralmente está a impedir o trabalho de terceiros
Make Your Build Self-Testing
Tradicionalmente uma build significa compilação, empacotamento, e todo o material adicional necessário para obter um programa para executar. Um programa pode ser compilar corretamente, até arrancar, mas isso não significa que ele faz o que correto.
Aqui podem ser colocados testes unitários, verificações de comprimento das regras de arquitetura definidas (um utilitário bom para este aspecto é por exemplo o Macker embora seja uma aplicação já descontinuada). Um outro exemplo de uma aplicação que nos permite efetuar verificações de arquitetura no código desenvolvido é o Sonar que pode facilmente ser integrado com servidores de continuous integration ou mesmo com um IDE de desenvolvimento. Podemos/devemos colocar mesmo testes funcionais e de integração.
Mais Informação sobre como forçar regras de arquitetura no código desenvolvido pode ser encontrada aqui.
Comecemos por uma abordagem sobre análise estática do código e por alguns utilitários conhecidos que permitem identificar os erros mais comuns na construção de software. O FindBugs e o PMD são dois bons exemplos para identificar este tipo de erros. Embora tenham uma parte bastante semelhante estas também se complementam muito bem.
O FindBugs é um programa open Source criado por Bill Pugh e David Hovemeyer que procura bugs em código Java. Ele usa análise estática para identificar centenas de diferentes tipos de potenciais de erros que opera sobre o bytecode do Java. O software é distribuído como uma aplicação gráfica, embora também existam plug-ins disponíveis para o Eclipse, Netbeans, Hudson, Jenkins. Por outro lado por ser usado a partir do Ant.
O PMD verifica programas feitos em código Java e procura por potenciais problemas como: Opções try / catch / finally vazias, código não utilizado, assim como variáveis locais não utilizadas. O PMD também têm a capacidade de detectar código duplicado.
As ferramentas do tipo xUnit são certamente um ponto de partida para tornar o código testável e estes testes são normalmente rápidos para se colocar numa build. São os exemplos mais conhecidos o Junit e Nunit.
Claro que além de testes unitários podemos colocar na build testes funcionais ou de integração e existem um conjunto interessante de ferramentas que permitem cumprir este objectivo. Assim são exemplo dessas ferramentas que se focam nos testes, e que podem e que podem com mais ou menos esforço ser usadas, o FIT, Selenium, Sahi, Watir, Fitnesse e ainda o TeStudio.
Everyone Commits To the Mainline Every Day
A utilização de commits frequentes obriga os programadores a dividirem o seu trabalho em pequenas porções. Esta opção ajuda a que se tenha uma melhor percepção do progresso. Geralmente ficamos com a impressão que não conseguimos fazer algo significativo no espaço de algumas horas. Mas uma boa organização, prática e metodologia ajuda-nos a conseguir cumprir estes objectivos.
Faz bastante sentido efetuar fusões frequentes porque estas são mais fáceis de integrar do que grandes, os ocasionais. Quanto menos vezes nós efetuarmos um commit a integração torna-se mais doloroso. Os problemas irão aparecer muito mais tarde, e irá ser gasto muito tempo na integração, porque vamos cair em “merges” complicados o que tornará sempre mais difícil descobrir o que quebrou a build. Porque estaríamos literalmente à procura de uma agulha num palheiro.
Every Commit Should Build the Mainline on an Integration Machine
Quando assim acontece ficamos logo a saber que foi que provocou um erro na build. Com a associação a um sistema de notificações que partiu a build fica a saber dessa situação uns minutos depois enquanto a memória ainda se encontra fresca e o problema é mais fácil de corrigir.
Este é provavelmente um dos item mais complicado de atingir, quando os recursos físicos do sistema são limitados. Existem opções de em vez de o fazer a cada commit o fazer apenas a cada X horas, mas claros estes factos dependem sempre do número de elementos envolvidos, da complexidade do sistema de build montado e da capacidade da(s) máquinas associada(s) ao servidor de integração contínua.
Keep the Build Fast
Um dos principais pontos da Integração Contínua é fornecer um feedback rápido, e nada é mais inoportuno para uma atividade de CI do que uma build que leva muito tempo a completar. Uma build de CI deve além de compilar o código, efetuar a atualização dos ambientes de desenvolvimento e testes de modo a que se esteja a trabalhar sobre as versões mais recentes do software. Nestes casos o processo de build e mais principalmente o processo de deploy deve ser o mais rápido possível para não ter o sistema indisponível durante um longo período de tempo.
Uma build de CI deve conter a compilação e deploy dos dados nos ambientes de teste (potencialmente nos ambientes de desenvolvimento, se conseguirmos ter o tempo de downtime extremamente curto). Em teoria uma build de continuous integration não deve ultrapassar os dez minutos.
Test in a Clone of the Production Environment
Um dos objectivos dos testes é identificar e eliminar, sob condições controladas, qualquer problema de que o sistema possa vir a ter em produção. Se for usado um ambiente diferente, cada diferença resulta num risco de algo que acontece em testes não vai acontecer em versões de produção. Como resultado queremos ter uma versão tão parecida quanto possível à versão de produção pois só assim são identificados os problemas reais e que realmente importam.
Para facilitar a criação de testes devem ser criadas user stories que simulem situações importantes para a utilização. Neste ponto será interessante experimentar as sugestões que o James Whittaker apresenta no livro Exploratory Testing para criar guias automatizados. Estes tours como são chamados no livro ajudam a criar um bom conjunto de situações de teste que, obviamente, depois têm de ser automatizados, o que pode não ser fácil.
Make it Easy for Anyone to Get the Latest Executable
Uma das partes mais difíceis do desenvolvimento de software é conseguir ter certeza de que estamos a criar o software certo. Não é fácil, aliás é bastante difícil, especificar os objectivos com antecedência e fazer a coisa correta: Normalmente as pessoas acham mais fácil de ver algo, mesmo que não está certo, e dizer como é que pode ser mudado ou melhorado. Um dos objectivos dos processos de desenvolvimento ágeis explicitamente tenta tirar proveito desta vertente do comportamento humano.
Quero instalar a última build onde a obtenho? Acho que basta para isso ter uma qualquer pasta partilhada na rede, de modo que acedendo à mesma seja fácil de obter
Everyone can see what's happening
CI gira à volta de uma boa comunicação, assim é esperado que o sistema garanta que todos possam facilmente ver o estado do mesmo, e que alterações foram feitas a ele. A utilização de emails de notificação quando uma build corre bem ou mal com os resultados dos testes é sempre uma necessidade (Principalmente quando esta falha para que possa ser reposta o mais rapidamente possível).
Automate Deployment
Para levar as práticas de CI a bom porto são sempre necessários vários ambientes, um para executar testes de confirmação, um ou mais para executar testes secundários. Como normalmente se está a movimentar artefactos entre a máquina de CI e os ambientes de teste com uma grande frequência, este processo deve ser feito de forma automática.
Um boa consequência de se ter o deploy automático, é que estes são depois facilmente adaptados para serem usados em ambientes de produção. Quando se automatizar os deploys em ambientes de produção será sempre bom fazer o mesmo com os processo de backup e rollback, porque infelizmente as coisas de vez em quando acabam por correr mal (O Murphy anda sempre à espreita. ).
Outra preocupação, geralmente, é a atualização das bases de dados associados ao software desenvolvido, quando são feitas releases frequentes. A utilização de utilitários como o mybatis-migrations ajuda a melhorar em muito este aspecto. O Martin Fowler e o Pramod Sadalage escreveram também um conjunto de dicas/sugestões que podem também ser bastante úteis e que pode ser encontrado aqui.
O que ganhamos com tudo isto?
Em geral, o maior benefício maior e mais abrangente de Integração contínua é a redução do risco, que advém da seu rápido feedback. O CI não faz com que os bugs desaparecem de vez, mas torna muito mais fácil a sua detecção e remoção. Estes quando chegam ao produto final levam a que o utilizador fique de algum modo zangado com o fornecedor do software, o que deteriora a imagem do mesmo.