Integração Contínua

Integração Contínua

O que é?

A Integração Contínua (CI – Continuous Integration) é um processo que exige que os programadores integrem, num repositório partilhado por toda a equipa, o código desenvolvido por si, pelo menos uma vez por dia. Num Universo utópico, as integrações deverão ocorrer várias vezes ao dia, essencialmente a cada ‘push’ de código para o repositório. Esta acção dispoletará uma ‘build’ automatizada.

Porquê?

As metodologias Agile exigem o código esteja sempre “preparado” para uma eventual entrega. Este processo de integração contínua do código de toda a equipa, na base de código em que se está a trabalhar, permite se detectar problemas bastante mais cedo.

Num processo Waterfall, cada entrega de código poderia demorar vários meses a acontecer. Como podes imaginar, nessa altura era recorrente que num dia de entrega se convocasse uma “sala de guerra”, onde deveria estar toda a equipa de desenvolvimento, a equipa de operações, a equipa de produto, a equipa de qualidade, a equipa de gestão. O processo de entrega era um verdadeiro caos, e poderia levar várias horas a decorrer (não contando com possíveis problemas que pudessem ocorrer, e que deveriam ser atacados de imediato pela equipa responsável).

De facto, nessa altura imaginar que a cada ‘push’ para o repositório poderia correr uma bateria de testes (unitários, integração, aceitação, …) e obter feedback se eventualmente as minhas alterações iriam partir alguma funcionalidade existente, era impensável. Uma verdadeira aventura !!

Como?

Existem imensas opções para quem quer começar a construir uma pipeline de desenvolvimento com integração contínua. O mais conhecido do momento talvez seja o Jenkins. Uma ferramenta open-source de CI escrita em JAVA. Para quem não quer adicionar mais uma ferramenta à, quase infindável, lista de ferramentas que se tem de gerir e administrar, existem várias opções SaaS. De momento, na log, utilizamos o SaaS Codeship da CloudBees.

Uma vez configurada a pipeline de integração contínua, sempre que ocorrer um “push” para um repositório Git, irá correr um processo automático onde será levantado um ambiente virtual com as características por nós definidas, dentro do qual irá ocorrer a “build” do projecto e posteriormente correrão os testes – unitários, integração, aceitação, mutação, …

Em qualquer um destes pontos, caso ocorra um qualquer problema, o processo de integração contínua falhará e teremos o relatório do problema. Importante referir que se o processo de integração falhar, não haverá qualquer possibilidade de se efectuar um deploy – automático ou manual – para qualquer que seja o ambiente.

Não é possível utilizar “eXtreme Programming” sem este processo !!

Desenvolvimento Orientado por Testes

TDD

Desenvolvimento Orientado por Testes

A metodologia “eXtreme Programming” promove o desenvolvimento orientado por testes (TDD). O quer isto dizer?

Contrariamente ao que a técnica do TDD aconselha, não é pouco-comum encontrar um programador que “ataca de cabeça” o desenvolvimento de uma qualquer funcionalidade.

Síndrome do cobertor curto

“Não vejo o mal nisso” poderás estar a pensar. Deixa-me então colocar a seguinte questão – já te aconteceu implementar/actualizar/corrigir uma funcionalidade e por obras de magia negra, outra funcionalidade “completamente ao lado” partiu? A isto se poderia chamar de síndrome do cobertor curto. Para taparmos de um lado, vamos destapar noutro.

test-driven development diagramO XP aconselha o desenvolvimento em ciclos curtos e em constante repetição. O programador deverá criar um teste e executá-lo. Este deverá falhar pois ainda não temos código real para ser testado. É só então que o código para a funcionalidade é criado e volta-se a executar o teste. Quando o teste passar o programador deverá, sem qualquer demora, revisitar o código escrito e refactorizá-lo. Este é o ciclo curto de desenvolvimento do XP.

Existem vários tipos de testes – unitários, integração, funcionais e aceitação. Todos eles, como iremos ver, são de especial importância no ciclo de vida do software.

Testes Unitários

Este tipo de teste, tal como o nome indica, deverá testar uma unidade de código. Tipicamente testará cada método público, em particular e sem qualquer tipo de contacto com o resto do sistema. Idealmente o que se testará é a correcta entrada e retorno de dados. Se eventualmente numa iteração futura, um programador alterar o comportamento desse método para aceitar ou retornar outro tipo de dados, o teste deverá automaticamente falhar.

É bastante comum se encontrar programadores a desenvolver na técnica – “testes orientados ao código”. É de primordial importância o desenvolvimento primeiro dos testes e somente depois o algoritmo. Fazer o processo inverso fará com que o código seja muito mais difícil de testar.

É interessante salientar que este tipo de testes, quando bem estruturados, também poderão servir como documentação do sistema. Quantos testes unitários se deve escrever por sistema? Bom, de acordo com o Robert “Uncle Bob” Martin, dever-se-á abranger 100% de todo o código !!!

Testes de Integração

Após a realização de testes unitários, onde se testaram unidades isoladas do sistema, é a altura de se misturar as peças. Existem várias aproximações a este tipo de teste – “big-bang”, “top-down”, “bottom-up”, “mixed” e “risky-hardest”. Deixarei a explicação destas abordagens para futuros artigos. No entanto, e se com os testes unitários se defende a cobertura de 100% do código, com estes testes a situação merece uma maior reflexão. O J.B.Rainsberger explicou como se poderá estar a criar um pequeno Monstro ao se utilizar testes de integração, pois facilmente se poderão chegar a milhares de testes. Aconselho a verem a apresentação (ou lerem o artigo) do Rainsberger.

Testes Funcionais

Os testes funcionais fazem parte do processo de Controlo de Qualidade (QA). Como o nome permite adivinhar, num teste funcional ir-se-á testar uma funcionalidade, de acordo com a especificação e requerimentos do sistema. O funcionamento interno não é tido em consideração, daí a designação de teste “caixa-negra”. Também estes testes têm várias abordagens.

Testes de Aceitação

Poderemos considerar a fase de testes de aceitação como o último momento para a libertação de um sistema de software. Estes são escritos a partir de “user stories” definidas pelo cliente. Um exemplo de um teste de aceitação num sistema de backend poderia ser:

  • o utilizador acede à página de login
  • o utilizador insere dados de autenticação (user: admin, pass: password)
  • o utilizador é reencaminhado para a página X

Basicamente ir-se-á testar possíveis cenários, mimicando utilizadores reais. Como estamos numa fase de pré-lançamento, o cliente e a equipa de QA deverão estar em uníssono sobre o que é pretendido testar.

Testes de Mutação

Este tipo de testes consiste em pequenas alterações no código base (mutações) que deverão ser detectadas e debeladas pela bateria de testes que estão a correr em cada commit. Como se pode depreender, esta tipologia de testes é primeiramente para testar a bateria de testes existentes e para auxiliar no desenho de novos testes.

O exemplo que se encontra na Wikipedia, transposto para PHP:

A mutação poderá ser somente transformar a condição lógica && para a condição ||.

Para a linguagem PHP podemos utilizar o framework Infection e para JavaScript poderemos recorrer ao framework Stryker.

 

Todos estes testes deverão ser automatizados e, de acordo com outro princípio do XP, deverão correr em contínua integração do código. Não te preocupes com este conceito, um artigo sobre o mesmo já está na calha.

Ficaste convencido que programar com recurso a testes é vantajoso? Partilha a tua opinião.