SVN e Jenkins: Integração contínua

Veja nesse artigo como a adoção da integração contínua com SVN e Jenkins colabora para diminuir os riscos de um projeto.

Fique por dentro
O artigo apresenta a prática de integração contínua, muito utilizada em ambientes que adotam o desenvolvimento ágil, porém, não é muito conhecido em meio a comunidade Delphi.

Sua aplicação objetiva a garantia de uma entrega de software melhor, com qualidade, através de rotinas automatizadas que vão desde compilação aos testes. Para demonstrar isso o artigo aborda a criação do ambiente ideal utilizando ferramentas gratuitas, como o Jenkins para a realização de Builds, o VisualSVN para controle de versões e DUnit para testes automatizados, tudo isso com um exemplo prático.


Guia do artigo:

Integração contínua é uma prática de metodologia ágil diretamente ligada ao XP (eXtreme Programming). A integração continua visa garantir que, qualquer alteração no código fonte seja rapidamente integrada e validada dentro da solução completa de forma transparente e automatizada. Caso uma alteração que tenha sido integrada acabe por gerar uma falha, o processo de integração continua deve garantir que o feedback seja imediato e preciso.

Para validar o resultado de um processo de integração contínua é possível mesclar inúmeras etapas. Diferente do que muitos pensam, integração continua não se resume somente ao build automatizado, mas sim, em uma série de etapas, com destaque para os testes automatizados.

Resumindo, segundo Martin Fowler: “Integração Contínua é uma prática de desenvolvimento de software onde os membros de um time integram seu trabalho frequentemente, geralmente cada pessoa integra pelo menos diariamente – podendo haver múltiplas integrações por dia.

Cada integração é verificada por um build automatizado (incluindo testes) para detectar erros de integração o mais rápido possível. Muitos times acham que essa abordagem leva a uma significante redução nos problemas de integração e permite que um time desenvolva software coeso mais rapidamente.”

A Figura 1 ilustra um exemplo de ciclo de integração contínua.

Figura 1. Exemplo de ciclo de integração contínua (I.C.).

Os benefícios na adoção de um processo de integração contínua são inúmeros, porém, podemos destacar alguns imediatos:

Veremos agora um pouco sobre cada ferramenta que será utilizada no artigo e quais são os seus propósitos.

Jenkins

O Jenkins é uma ferramenta multiplataforma Open Source voltada para a integração contínua, baseada (Fork – ver BOX 1) no projeto Hudson. Basicamente o Jenkins funciona como um servidor de integração continua onde é possível programar Jobs para diversos projetos, inclusive sendo possível criar integrações entre esses Jobs.

Não entraremos em muitos detalhes sobre o Jenkins por não ser o foco deste artigo, mas é altamente recomendado visitar o site oficial (seção Links) e se associar ao grupo brasileiro sobre a ferramenta (jenkinsci-br@googlegroups.com). Uma das vantagens do Jenkins é a sua comunidade extremamente forte.

Neste artigo iremos utilizar a sua distribuição para o ambiente Windows visando uma melhor integração com o Delphi.

BOX 1. Fork

O Fork, recurso do GitHub, consiste em criar uma cópia independente de um determinado repositório, desta forma, é possível trabalhar em um fork de um projeto, sem que as alterações afetem o projeto original. Esta é a forma utilizada para contribuir em projetos open source.

Após as alterações serem finalizadas no fork, é possível realizar um pull request no repositório original, com isso o dono do projeto pode tomar a decisão de acoplar ou não as alterações do fork ao projeto original.

Subversion (SVN)

O Subversion é um dos sistemas controladores de versão mais famosos atualmente. Utilizaremos ele como nosso repositório neste artigo por ser extremamente simples e integrar perfeitamente com o Delphi e com o Jenkins (outra vantagem do Jenkins é sua vasta gama de Plugins que permite a integração com os mais diversos sistemas controladores de versão como por exemplo o GIT).

Iremos criar um servidor Subversion Windows e nele iremos criar nosso repositório, para isso, utilizaremos o VisualSVN Server que pode ser obtido no site disponível na seção Links.

MSBuild

O MSBuild é a plataforma de compilação da Microsoft que pode ser utilizada em conjunto com o compilador do Delphi. Utilizaremos um Plugin para o Jenkins que se integra com o MSBuild afim de realizar os processos de compilação dos nossos Jobs de projetos Delphi.

O MSBuild já é disponibilizado no Windows e pode ser encontrado no diretório C:\Windows\Microsoft.NET\Framework\[VERSAO]\MSBuild. Neste artigo utilizaremos a versão v3.5.

Instalando o SVN e criando um repositório

Finalmente iremos colocar a mão na massa, começando por nosso repositório no SVN. Para isso, iremos instalar o VisualSVN Server, uma versão do SVN para Windows.

Basicamente seu processo de instalação se resume ao padrão Windows (next, next e finish). A única observação se dá na escolha da edição (VisualSVN Server Editions), como pode ser visto na Figura 2. Nesta tela selecione a opção Standard Edition que é a versão gratuita.

Figura 2. Instalação do VirtualSVN Server.

Após a instalação, será apresentada uma tela como a da Figura 3, onde será possível criar nossos usuários e nossos repositórios.

Figura 3. VisualSVN Server.

Criaremos dois usuários, um para o desenvolvedor e outro para o Jenkins, para isso, clique com o botão direito do mouse em Users e depois clique em Create User. Após preencher o nome de usuário e senha, o usuário já está criado, agora basta criarmos o repositório. Para isso, clique com o botão direito do mouse em Repositories e depois clique em Create New Repository

Após preencher seu nome, serão apresentadas as opções de estrutura inicial do repositório, selecione a opção Single-Project Repository para que já seja criada uma estrutura padrão (Branches, Trunk e Tags). Após isso, informe que todos os usuários têm acesso completo ao repositório e o processo estará finalizado (Figura 4).

Figura 4. Repositório Criado.

Fazendo o primeiro Checkout

Para se trabalhar com o SVN, é preciso fazer um checkout do repositório em sua máquina, ou seja, fazer uma cópia de trabalho do repositório. Para isso utilizaremos o próprio Delphi que já possui integração nativa com o SVN.

No Delphi, em File->Open From Version Control, coloque a url do repositório e o diretório da cópia de trabalho para realizar o checkout, conforme é demonstrado na Figura 5.

Figura 5. Realizando Checkout.

Pronto, agora basta criar o projeto e salvar no diretório da sua cópia local. Vale uma observação, o SVN trabalha com o conceito de Branches para a cópia de trabalho, Trunk para a última versão estável e Tags para as versões antigas.

Criando o projeto em Delphi

Iremos criar um projeto em Delphi simples que se resume a uma classe somente que será utilizada para validar nossos conceitos.

Esta classe deverá realizar cálculos relacionados a desconto e posteriormente, iremos criar uma rotina de automatização de testes para esta.
Crie um projeto do tipo VCL Forms Application ou FireMonkey HD Application e dê o nome de ClubeDelphi. Inclua uma unit chamada untDesconto e nesta codifique a classe TDesconto, conforme a Listagem 1.

Listagem 1. Classe que realiza cálculos de descontos.

01 unit untDesconto; 02 interface 03 type 04 TDesconto = class 05 strict private 06 FValorTotal: Real; 07 FValorFinal: Real; 08 FDescontoValor: Real; 09 FDescontoPorcentagem: Real; 10 procedure SetValorTotal(const Value: Real); 11 public 12 function AplicarDescontoPorcentagem(const APorcentagem : Real):Real; 13 function AplicarDescontoValor(const AValor : Real):Real; 14 property ValorTotal : Real read FValorTotal write SetValorTotal; 15 property DescontoPorcentagem : Real read FDescontoPorcentagem; 16 property DescontoValor : Real read FDescontoValor; 17 property ValorFinal : Real read FValorFinal; 18 end; 19 implementation 20 function TDesconto.AplicarDescontoPorcentagem( const APorcentagem: Real): Real; 21 begin 22 Self.FDescontoPorcentagem := APorcentagem; 23 Self.FDescontoValor := (APorcentagem * Self.FValorTotal) / 100; 24 Self.FValorFinal := Self.FValorTotal - Self.FDescontoValor; 25 Result := Self.FValorFinal; 26 end; 27 function TDesconto.AplicarDescontoValor(const AValor: Real): Real; 28 begin 29 Self.FDescontoValor := AValor; 30 Self.FDescontoPorcentagem := (AValor * 100) / Self.FValorTotal; 31 Self.FValorFinal := Self.FValorTotal - Self.FDescontoValor; 32 Result := Self.FValorFinal; 33 end; 34 procedure TDesconto.SetValorTotal(const Value: Real); 35 begin 36 FValorTotal := Value; 37 Self.AplicarDescontoValor(Self.FDescontoValor); 38 end; 39 end.

Como podemos ver a classe TDesconto, basicamente calcula descontos por porcentagem e por valor. Antes de prosseguirmos com o projeto de testes automatizados, iremos incluir nosso projeto ClubeDelphi em nosso repositório do SVN, para isso, salve o projeto no diretório onde foi feito o checkout do nosso repositório do SVN (Figura 5). Após isso, clique com o botão direito do mouse no projeto em Project Manager e depois em Add to Version Control, conforme a Figura 6.

Figura 6. Adicionando arquivos ao repositório.

Feito isso, nosso projeto já faz parte do repositório. Agora iremos criar o projeto de testes.

Criando o projeto de testes automatizados

Um processo de integração contínua não faz sentido se não possuir uma rotina de testes automatizados, pois só o fato de realizar um Build em um projeto, não garante que ele está livre de falhas. Para Delphi existe o projeto Open Source DUnit que já vem acoplado a IDE desde algumas versões mais antigas. Para mais informações sobre, veja a seção Links ao final do artigo.

Criar um projeto de testes é algo muito simples, basta acessar o menu em File>New>Other>Unit Test>Test Project. Será apresentada uma tela semelhante à Figura 7.

Figura 7. Criando um projeto de testes automatizados.

Basta informar o projeto principal (Source Project), o nome do projeto de testes automatizados (Project Name) e a localização dos arquivos do projeto de testes automatizados (Location). Assim, o projeto de testes automatizados está criado.

Faremos então o nosso primeiro caso de testes, baseado na classe TDesconto Novamente acesse o menu File>New>Other>Unit Test>Test Case. Será apresentada uma tela semelhante à Figura 8.

Figura 8. Criação do caso de testes.

Estes Wizards facilitam muito a vida de quem está começando a lidar com a DUnit, porém, nada impede que você crie seus testse do zero, basta conhecer o framework da DUnit. Após a criação do caso de testes, teremos uma unit com o nome de TestUntDesconto contendo a classe TestTDesconto com o esqueleto de uma rotina de automatização de testes baseada na classe TDesconto. Após algumas alterações na classe TestTDesconto, esta deve ficar semelhante à Listagem 2.

Listagem 2. Classe que realiza cálculos de descontos.

01 unit TestUntDesconto; 02 interface 03 uses TestFramework, untDesconto; 04 type 05 TestTDesconto = class(TTestCase) 06 strict private 07 FDesconto: TDesconto; 08 public 09 procedure SetUp; override; 10 procedure TearDown; override; 11 published 12 procedure TestAplicarDescontoPorcentagem; 13 procedure TestAplicarDescontoValor; 14 procedure TestAtualizacaoDeTotal; 15 end; 16 implementation 17 procedure TestTDesconto.SetUp; 18 begin 19 FDesconto := TDesconto.Create; 20 FDesconto.ValorTotal := 250; 21 end; 22 procedure TestTDesconto.TearDown; 23 begin 24 FDesconto.Free; 25 FDesconto := nil; 26 end; 27 procedure TestTDesconto.TestAplicarDescontoPorcentagem; 28 var 29 ReturnValue: Real; 30 APorcentagem: Real; 31 begin 32 APorcentagem := 10; 33 ReturnValue := FDesconto.AplicarDescontoPorcentagem(APorcentagem); 34 CheckEquals(225,ReturnValue,'Retorno do método'); 35 CheckEquals(225,FDesconto.ValorFinal,'Valor final'); 36 CheckEquals(25,FDesconto.DescontoValor,'Desconto em valor'); 37 CheckEquals(10,FDesconto.DescontoPorcentagem,'Desconto em porcentagem'); 38 end; 39 procedure TestTDesconto.TestAplicarDescontoValor; 40 var 41 ReturnValue: Real; 42 AValor: Real; 43 begin 44 AValor := 25; 45 ReturnValue := FDesconto.AplicarDescontoValor(AValor); 46 CheckEquals(225,ReturnValue,'Retorno do método'); 47 CheckEquals(225,FDesconto.ValorFinal,'Valor final'); 48 CheckEquals(25,FDesconto.DescontoValor,'Desconto em valor'); 49 CheckEquals(10,FDesconto.DescontoPorcentagem,'Desconto em porcentagem'); 50 end; 51 procedure TestTDesconto.TestAtualizacaoDeTotal; 52 begin 53 FDesconto.AplicarDescontoValor(35); 54 CheckEquals(215,FDesconto.ValorFinal); 55 FDesconto.ValorTotal := 350; 56 CheckEquals(35,FDesconto.DescontoValor,'Desconto $ pós atualização'); 57 CheckEquals(10,FDesconto.DescontoPorcentagem,'Desconto % pós atualização'); 58 CheckEquals(315,FDesconto.ValorFinal,'Valor final pós atualização'); 59 end; 60 initialization 61 RegisterTest(TestTDesconto.Suite); 62 end.

O objetivo desse código não é abordar em si o que a unit TestDesconto faz, mas sim o uso do DUnit. Para isso, vale conhecermos a respeito de alguns conceitos básicos. As classes de caso de teste devem descender da classe TTestCase para que seja possível registrar a suíte de testes da classe.

Os métodos SetUp (linhas 17 a 21) e TearDown (linhas 22 a 26) podem ser sobrepostos (override) caso sejam necessários. Esses são executados sempre antes (SetUp) e depois (TearDown) da execução de qualquer método de teste. Metodologias ágeis defendem que cada teste deve ser independente, por isso, é possível criar e liberar a instancia de um objeto a cada teste, prevenindo assim qualquer possível interferência entre os testes, para isso, basta usar o método SetUp e o método TearDown.

Os métodos de teste devem estar na sessão published da classe de caso de testes, para que assim, através da RTTI o framework da DUnit consiga executá-los.

Voltando a falar da nossa classe TestTDesconto, ela basicamente testa as 3 possibilidades da classe TDesconto, aplicação de desconto por porcentagem (linhas 27 a 38), desconto por valor (linhas 39 a 50) e atualização do valor total com recálculo dos descontos (linhas 51 a 59).

Para ver nossos testes em ação, basta executar o aplicativo. Essa ação deverá apresentar algo como a Figura 9.

Figura 9. DUnit em ação.

Mais sobre o SVN no Delphi

Finalizado o projeto de testes, é chegada a hora de adicioná-lo ao nosso repositório no SVN, para isso, repita o processo de Add to Version Control, como demonstrado na Figura 6.

Feito isso, nosso repositório já sabe da existência dos arquivos do nosso projeto, agora precisamos realizar o commit das alterações. O commit é o processo onde as alterações locais são efetivadas no repositório principal.

Para realizar o commit a partir do Delphi, basta clicar com o botão direito do mouse no projeto em Project Manager e depois clicar em Subversion>Commit>From Project Directory. Será apresentada uma tela semelhante à Figura 10.

Figura 10. Arquivos que foram alterados após o último commit.

Nos casos em que um membro da equipe realizar um Commit no repositório e sua cópia passar a ficar desatualizada, basta realizar o processo de Update. O Update é o processo oposto ao Commit, se no Commit estamos efetivando nossas alterações locais no repositório principal, no Update estamos atualizando nossa cópia local se baseando na cópia do repositório. No Delphi esta opção fica em Subversion>Update>From Project Directory, ou ao clicar de direito sobre o projeto no Project Manager.

Integração continua com Jenkins

Vamos agora para o nosso "maestro", que irá orquestrar toda a nossa obra, o poderoso servidor de integração contínua Jenkins.

Lembrando que o Jenkins não é a única opção de servidor de integração continua no mercado, como é o caso do Final Builder, um excelente servidor de integração que já vem (em uma versão um pouco mais limitada) junto a algumas versões do Delphi.

A instalação do Jenkins não possui relativas dificuldades. Depois de instalado, o Jenkins passa a rodar como um serviço do Windows e responde com um servidor HTTP na porta 8080 (Figura 11).

Figura 11. Tela inicial do Jenkins

Com o Jenkins instalado e funcionando, podemos instalar o Plugin do MSBuild para que seja possível compilar nossas aplicações Delphi. Na lateral esquerda da tela principal (Figura 11) clique sobre o menu Gerenciar Jenkins. Este é o painel de controle do Jenkins. Agora vá em “Gerenciar Plugins>Disponíveis” e na caixa de filtro, digite MSBuild. Marque para instalar o Plugin e clique em “Baixar agora, instalar e depois reiniciar”.

Feito isso, o MSBuild já estará instalado e, caso haja algum problema na instalação automática do plugin, é possível realizar o download manualmente (ver seção Links) e realizar a instalação em “Gerenciar Plugins>Avançado>Upload”.

Porém, se você usar proxy na sua rede, pode ser que ocorra algum erro para realizar o download, caso você se depare com esse cenário, vá em “Gerenciar Plugins> Avançado e realize a configuração de proxy do Jenkins.

Outro Plugin necessário é o EnvInject (seção Links) que basicamente injeta variáveis de ambiente ao processo de build de um projeto. Instale-o da mesma forma como foi instalado o MSBuild.

Configurando o MSBuild

Com nossos Plugins instalados no Jenkins, vamos realizar as configurações do MSBuild na opção “Gerenciar Jenkins>Configurar o sistema>MSBuild Instalações”. Nesta sessão, iremos especificar as informações sobre o MSBuild instalado no servidor que iremos utilizar. Os parâmetros que iremos configurar são os seguintes:

  • Name: Um nome de identificação para o Plugin instalado, já que podem ser configurados mais de um MSBuild.
  • Path do MSBuild: Caminho completo do binário do MSBuild, geralmente encontrado em “C:\Windows\Microsoft.NET\Framework\[versão]\MSBuild.exe”

Criando um Job

Primeiramente iremos criar um Job simples que somente baixe os fontes do SVN e compile o projeto, depois iremos aprimorando. Clique em “Novo Item” na lateral esquerda do Jenkins, informe o nome do Job e selecione a opção “Construir um projeto de software free-style”.

Na tela de parametrização do Job, na opção “Gerenciamento de código fonte”, selecione o Subversion. Ao realizar essa seleção, serão apresentadas as parametrizações do gerenciador. Para o Subversion, iremos configurar somente o diretório do nosso repositório, algo como https://[servidor]/svn/clubedelphi/branches/.

Caso o Jenkins não consiga estabelecer conexão com o repositório, será solicitado o usuário e senha de acesso através do link “Enter credential”. Especifique o usuário criado no SVN para ser usado no Jenkins com o tipo de autenticação “username/password”.

Agora na opção “Ambiente de Build”, marque a opção “Inject environment variable to the build process” (essa opção está disponível graças ao Plugin EnvInject) e em “Properties Content” copie o conteúdo do arquivo rsvars.bat sem os @SET antes do nome das variáveis.

Esse arquivo já vem com o Delphi, geralmente no diretório “C:\Program Files (x86)\Embarcadero\RAD Studio\[versão]\bin”. Essas variáveis de ambiente são necessárias no momento do Build de aplicações Delphi pelo MSBuild.

Finalmente chega a hora de configurar os processos de Build. Na tela de parametrização do Job, vá em “Adicionar passo no build>Build a Visual Studio project or solution using MSBuild”. Em “MSBuild Version”, selecione a instalação do MSBuild configurada anteriormente, em “MSBuild Build File”, digite o nome do arquivo dproj do projeto que será compilado, no caso ClubeDelphi.dproj e por fim, em “Command Line Arguments”, coloque o seguinte texto: “/v:d /target:Build /p:config=Debug”, com isso estamos especificando que faremos um build do projeto no modo Debug.

Com essas configurações feitas, deveremos ter algo como a Figura 12.

Figura 12. Parametrização do Job.

Salve as configurações e clique em “Construir agora na lateral esquerda, para testar o Job. Se tudo ocorrer como esperado, deverá aparecer um círculo azul no histórico de Builds dentro das opções do Job, ou no grid da Dashboard do Jenkins, isso significa que o processo foi bem sucedido, caso algo dê errado, esse círculo deverá ser vermelho. Observe o exemplo na Figura 13.

Figura 13. Histórico de Builds.

Executando Testes pelo Jenkins

Um processo de integração contínua não está completo se não existirem testes unitários automatizados. Sendo assim, nosso projeto de testes já está pronto, porém, da forma como o Delphi cria o projeto de teste da DUnit através de seu Wizard, o mesmo não está pronto para a execução por linha de comando.

A Listagem 3 apresenta o código do arquivo .dpr recém gerado de um projeto de teste da DUnit.

Listagem 3. Projeto de testes.

01 program ClubeDelphiTests; 02 {$IFDEF CONSOLE_TESTRUNNER} 03 {$APPTYPE CONSOLE} 04 {$ENDIF} 05 uses 06 DUnitTestRunner, 07 TestUntDesconto in 'TestUntDesconto.pas', 08 untDesconto in '..\untDesconto.pas'; 09 {$R *.RES} 10 begin 11 DUnitTestRunner.RunRegisteredTests; 12 end.

Porém, precisamos que o aplicativo de testes funcione com interface gráfica se for executado por uma pessoa, e também como console, se for executado pelo Jenkins. Assim sendo, faremos algumas alterações neste arquivo para que o mesmo suporte as duas opções. O código deve ser feito como descrito na Listagem 4.

Listagem 4. Projeto de testes.

01 program ClubeDelphiTests; 02 {$IFDEF CONSOLE_TESTRUNNER} 03 {$APPTYPE CONSOLE} 04 {$ENDIF} 05 uses 06 TestFrameWork, 07 TextTestRunner, 08 GUITestRunner, 09 TestUntDesconto in 'TestUntDesconto.pas', 10 untDesconto in '..\untDesconto.pas'; 11 {$R *.RES} 12 var 13 Results: TTestResult; 14 begin 15 if ParamStr(1) = 'jenkins' then 16 begin 17 Results := TextTestRunner.RunRegisteredTests; 18 try 19 if Results.FailureCount + Results.ErrorCount = 0 then 20 ExitCode := 0 21 else 22 ExitCode := 1; 23 finally 24 Results.Free; 25 end; 26 end 27 else 28 GUITestRunner.RunRegisteredTests; 29 end.

Embora esse código seja relativamente grande, é relativamente simples. Devemos atentar principalmente a partir da linha 15. Nota-se que é verificado se foi recebido um parâmetro denominado ‘jenkins’. Caso ele exista, a execução ocorrerá da linha 17 a 25, onde é executado o método RunRegisteredTests de TextTestRunner (linha 17) em modo console, caso contrário, o mesmo método é executado, porém de GUITestRunner (linha 28), utilizando interface gráfica.

Voltando ao Jenkins, faremos agora com que nosso Job saiba da existência do projeto de testes e passe a considerá-lo, mas, antes disso, deve-se realizar o commit das alterações do projeto para o SVN.

Nas configurações do Job inclua mais dois passos de Build, o primeiro será um “Build a Visual Studio project or solution using MSBuild”, como feito anteriormente, mas dessa vez, apontando para o projeto de testes, ou seja, no campo MSBuild File deverá estar o valorTest\ClubeDelphiTests.dproj”. Posicione esse Build como sendo o primeiro da lista.

O segundo passo de Build a ser adicionado é do tipo “Executar no comando do Windows”. Esse será o passo onde iremos executar nosso teste. No campo para especificar o que será executado, informe a instrução: “Test\Win32\Debug\ClubeDelphiTests.exe jenkins”. Ou seja, através de linhas de comando estaremos executando a aplicação de testes passando o parâmetro 'jenkins'. Posicione esse passo de build como sendo o segundo.

Agora o build do Job deverá conter os seguintes passos: “Compilação do projeto de testes>Execução dos testes>Compilação do projeto principal”. Quando qualquer um destes passos falharem, o seguinte não será executado.

Salve as alterações e execute o Job clicando em Construir agora”. Neste momento, perceba que nada de diferente aconteceu, isso porque todos os testes estão obtendo sucesso. Para validar o ciclo, na unit TestUntDesconto.pas do projeto de testes, localize o método TestTDesconto.TestAplicarDescontoPorcentageme altere a linha:

APorcentagem := 10;

para

APorcentagem := 11;

Faça o commit das alterações e execute o Job. Sim, agora temos um círculo vermelho. Isso significa que algo falhou (mas para nós significa que o processo está coerente).

Para saber mais sobre a execução do Build, clique no link da execução do Build (o link que possui o círculo com a cor de status, o número de Build, e a data e hora de execução) e depois clique em Saída do Console. Para o Job, a saída do Build provavelmente ocorrerá na seguinte ordem: execução do Plugin EnvInject, atualização do repositório do SVN, compilação do projeto de testes e finalmente, a execução dos testes com o texto da Listagem 5.

Listagem 5. Resultado dos testes.

DUnit / Testing ...F.. Time: 0:00:00.0 FAILURES!!! Test Results: Run: 3 Failures: 1 Errors: 0 There was 1 failure: 1) TestAplicarDescontoPorcentagem: ETestFailure at "Retorno do método, expected: <225> but was: <222,5>" C:\Program Files (x86)\Jenkins\jobs\ClubeDelphi\workspace>exit 1 Build step 'Executar no comando do Windows' marked build as failure Finished: FAILURE

Com isso comprovamos que nosso ciclo de integração continua está funcionando por completo. Porém, há como aprimorar ainda mais a saída de console dos testes, para isso, é possível utilizar o Plugin NUnit para o Jenkins, que é específico para interagir com os resultados de testes no padrão NUnit.

Instale esse plugin através da listagem de plugins disponíveis do jenkins, caso ele não esteja disponível nesta listagem, você pode baixar a sua última versão pelo GitHub (seção Links). A DUnit que vem com o Delphi já está preparada para exportar os resultados dos testes registrados para um arquivo XML, porém, esse arquivo não está no padrão esperado pelo plugin NUnit do Jenkins.

Para resolver este problema foi criada por Mark Pickersgill a unit “XMLTestRunner2pas” que basicamente substitui a unit TextTestRunnerpas em nosso exemplo. Baixe essa unit (seção Links) e adicione a biblioteca da DUnit do Delphi (geralmente em C:\Program Files (x86)\Embarcadero\RAD Studio\[versão]\source\DUnit\src).

Com todas ferramentas preparadas para interagir com o padrão NUnit nos resultados dos testes, é necessário adaptar a aplicação e o Job para tirar proveito deste recurso. Começaremos pelo projeto de testes. Basicamente a alteração necessária será substituir a unit TextTestRunner pela XMLTestRunner2 em nosso arquivo DPR e depois especificar o local de saída para o arquivo XML gerado. Após a alteração, o código do projeto de testes deve ficar semelhante ao da Listagem 6.

Listagem 6. Projeto de testes atualizado.

01 program ClubeDelphiTests; 02 {$IFDEF CONSOLE_TESTRUNNER} 03 {$APPTYPE CONSOLE} 04 {$ENDIF} 05 uses 06 TestFrameWork, 07 XMLTestRunner2, 08 GUITestRunner, 09 TestUntDesconto in 'TestUntDesconto.pas', 10 untDesconto in '..\untDesconto.pas'; 11 {$R *.RES} 12 var 13 Results: TTestResult; 14 begin 15 if ParamStr(1) = 'jenkins' then 16 begin 17 Results := XMLTestRunner2.RunRegisteredTests('DUnitResult.xml'); 18 try 19 if Results.FailureCount + Results.ErrorCount = 0 then 20 ExitCode := 0 21 else 22 ExitCode := 1; 23 finally 24 Results.Free; 25 end; 26 end 27 else 28 GUITestRunner.RunRegisteredTests; 29 end.

Note que o código é o mesmo da anterior, diferindo apenas na linha 17, onde é utilizada a unit XMLTestRunner2.

Faça o commit das alterações para o SVN. Por fim, iremos adaptar nosso Job para ter conhecimento do arquivo DUnitResult.xml que será gerado a cada execução dos testes. Nas configurações do Job, adicione um passo pós-build em “Add post-build action”, esse passo será do tipo “Publish NUnit test result report” (essa opção está disponível graças à instalação do plugin NUnit).

Note que a única configuração do passo é o caminho do arquivo XML. Informe o nome que especificado no projeto de testes, ou seja, “DUnitResult.xml”. Salve as configurações e execute o Job.

Note que agora existe uma série de indicadores de testes, baseados no resultado do último Build. Na página principal do Job agora é possível ver o último resultado de teste e também é exibido um gráfico de Tendência de resultados de teste conforme a Figura 14.

Figura 14. Plugin NUnit em ação.

Automatizando a execução de Jobs

Até agora, toda execução de Jobs que fizemos foi através de um processo de acionamento manual, com certeza esse não é o cenário ideal para o dia a dia. Podemos resolver essa questão de duas maneiras (e inclusive combiná-las), a primeira é através de agendamento de execução de Jobs e a segunda é através de scripts de hook do SVN.

Nas configurações de um Job, existe a opção “Trigger the builds>Construir periodicamente”, ao marcar essa opção será disponibilizado um campo para digitar as configurações de agenda. Para quem está familiarizado com o Linux, os comandos de agendamento de Jobs do Jenkins seguem o padrão Cron (com pequenas diferenças), por exemplo, para programar um Job para ser executado de hora em hora, basta utilizar o texto: “1 * * * *” que significa que será executado "no minuto 1 de toda hora, todo dia, todo mês e todo dia da semana".

Para não executar um Job de forma desnecessária, é possível condicionar sua execução a uma possível alteração no repositório, para que isso seja possível, basta realizar a mesma configuração na opção “Consultar periodicamente o SCM” e descartar a opção “Construir periodicamente”.

A outra opção é utilizar os Hooks do SVN. Os Hooks são rotinas a serem executadas entre eventos de um repositório do SVN. Por exemplo, é possível programar uma rotina para ser executada logo após um Commit em um determinado repositório, em termos técnicos estaríamos configurando um Hook de Post-Commit. No ambiente Windows os Hooks podem ser tanto um executável como um script .bat, enquanto que no ambiente Unix poderia ser um script python, um script shell, um binário C e etc.

O Jenkins possui uma API REST própria, que entre outras coisas, permite que, via HTTP-GET seja possível realizar a construção (Build) de um projeto. A URL desta API segue o seguinte Template: “http://[servidor_jenkins]/job/[nome_do_job]/build”. Faremos proveito deste recurso para que, quando um Commit seja disparado em nosso repositório, seja acionado o Build do Job no Jenkins.

Para nos auxiliar neste processo, usaremos o aplicativo WGET (seção Links). Este é um aplicativo Free extremamente simples que dentre outras coisas, executa requisições HTTP por linha de comando.

Com o WGET disponível no servidor, vamos finalmente programar o nosso Hook, no VisualSVN Server (Figura 15), sendo assim, clique de direita no repositório e depois selecione a opção “Todas as tarefas -> Manage Hooks”.

Figura 15. Manage Hooks.

Será apresentada uma lista com todos os possíveis Hooks. Utilizaremos o Post-Commit Hooks, hook executado logo após o Commit neste repositório. Marque o Hook e depois clique em editar. Respeitando os caminhos configurados em seu ambiente, configure o comando da Listagem 7 como sendo o comando do Hook.

Listagem 7. Post-Commit Hook.

C:/Projetos/wget.exe -b http://localhost:8080/job/ClubeDelphi/build

O parâmetro –b indica que o WGET irá realizar a requisição em Background. Sendo assim o processo foi finalizado, agora ao realizar qualquer Commit em seu repositório, automaticamente será iniciado o processo de Build do seu Job no Jenkins.

Pensando nos benefícios

As possibilidades dentro da integração contínua são diversas, sendo um dos principais pilares no processo de ALM (Application Life Management). Em integrações manuais, o dia a dia de uma equipe que trabalha em um projeto relativamente mais sofisticado se torna cada vez mais complicada ao entrarem em processos de geração de Build, testes, montagens e etc.

Imagine realizar a montagem de um pacote resultante de mais de 20 Commits, se ocorrer um erro durante os testes, seria fácil descobrir o responsável? Seria fácil retomar o raciocínio de mais de cinco Commits atrás?

A adoção da integração contínua colabora para diminuir os riscos de um projeto, a ponto que, ao final de cada dia, é possível se certificar que toda evolução o projeto foi implementada, testada, integrada e possivelmente publicada em um ambiente de testes ou até mesmo de produção, fazendo o que é conhecido como entrega contínua.

Confira também

Artigos relacionados