O desenvolvimento de software é composto por diversas fases e atividades que tem como objetivo entregar um software de qualidade, sem erros e no melhor tempo possível.

Uma dessas atividades é o teste de unidade, que é uma habilidade muito importante para essa entrega com qualidade, pois permite a verificação automática do software para garantir que tudo está funcionando conforme desejado.

E dentre as diversas etapas do desenvolvimento de um software existem ferramentas que podem auxiliar o desenvolvedor na entrega de um produto muito melhor. Saber escolhe-las é um passo fundamental, pois elas também podem atrasar o projeto final.

Os Builds (compilação, configuração e deploy de aplicações) mais simples, por exemplo, podem ser feitos através da própria IDE, como Eclipse, pois faz uso de um script ou XML, mas o usuário normalmente não tem conhecimento do que está ocorrendo por trás. Contudo, quando os projetos se tornam maiores e mais complexos, os builds automáticos se tornam necessários. Daí, trabalhar com ferramentas que automatizam todo o processo de build pode economizar muito tempo e trabalho, ao invés de persistir em ficar na própria IDE. Em outros casos, a melhor alternativa é a utilização ferramentas de integração contínua, que fazem ainda mais além do que as ferramentas de build, adicionando mais etapas e automatizando ainda mais o processo como um todo.

Todas essas possibilidades precisam ser bem avaliadas pelos desenvolvedores dentro de um projeto de software e, para isso, toda habilidade e conhecimento do que existe e do que está sendo usado no mercado é essencial.

No restante do artigo serão analisadas e estudadas diversas ferramentas utilizadas no apoio ao desenvolvimento de software atual, além de comparações e dicas de como operá-las e configurá-las.

Ferramentas de Teste de Software

O teste de unidade é responsável por testar um pedaço do software. Estes ocorrem em nível de desenvolvimento para assegurar que o código escrito para alguma funcionalidade desejada funciona conforme o esperado. Assegurar que cada unidade da aplicação passa nos testes garante ao desenvolvedor que qualquer código que tenha sido inserido ou alterado após a última bateria de testes não afetou a operação atual do software.

Diferente de outros tipos, o teste de unidade pode ser automatizado e para isso existem diversas ferramentas que o suportam, sendo uma das principais o JUnit. Ele frequentemente exige um projeto separado no workspace do desenvolvedor ou então uma solution separada.

JUnit

O JUnit é o framework padrão para testes de unidade na plataforma Java e tem como benefício a integração com o Eclipse.

Para avaliarmos esse benefício, segue na Listagem 1 uma classe e um método em Java que calcula o imposto de um produto e retorna o valor final deste produto.

Listagem 1. Exemplo de código em Java para testar com JUnit.


  package devmedia.testeunidade;
  public class ItemImpostoCalc
  {
           public static double CalcItemImp(double precoItem, double imposto)
           {
                     return precoItem + imposto;
           }
  }

Para testar este método basta utilizar o JUni,t conforme exemplo da Listagem 2.

Listagem 2. Exemplo de teste realizado com o JUnit.


  package devmedia.testeunidade;
   
  import static org.junit.Assert.*;
  import org.junit.Test;
  import devmedia.testeunidade.*;
   
  public class ItemImpostoCalcTeste
  {
  @Test
  public void testeCalcItemImp()
  {
  double expected = 105.0;
  assertEquals(expected,
  ItemImpostoCalc.CalcItemImp(100.00, 5.00));
  }
  }

Pode-se notar que o método assertEquals é responsável por testar o método da classe base. Diferente do JUnit 3, que era necessário estender a classe TestCase, no JUnit 4 basta incluir a anotação @Test. Para executar o teste basta ir em Run As e selecionar JUnit Test. Os resultados do teste são exibidos na JUnit View do Eclipse.

Segue na Figura 1 uma imagem do JUnit mostrando a barra verde, que indica que os testes foram executados com sucesso.

Tela do
JUnit informando que os testes foram
executados com sucesso

Figura 1. Tela do JUnit informando que os testes foram executados com sucesso.

Caso seja encontrado algum erro, o JUnit mostra uma barra vermelha e indica onde foi encontrado o erro no código, conforme mostra a Figura 2.

Tela do JUnit informando que os testes geraram erros

Figura 2. Tela do JUnit informando que os testes geraram erros.

Ferramentas de Build

Quando é necessário realizar a compilação de um projeto esse processo normalmente é realizado de forma manual utilizando a IDE. O Eclipse possui atalhos e opções rápidas no seu menu para compilar a aplicação. Outra situação é quando a aplicação possui componentes dependentes, como assemblies externos ou arquivos JAR, em que esses artefatos devem ser copiados manualmente para o diretório compartilhado.

A partir do momento que o projeto se torna maior e mais complexo é necessário utilizar um mecanismo diferente para economizar tempo e trabalho. Um sistema de build automático normalmente é empregado nesses casos em que o projeto é mais completo e o tempo para desenvolvimento deve ser otimizado. Nesses casos um sistema de integração contínua pode ser incluído optativamente, visto que as tarefas de build são mais automatizadas, mas isso será mais bem analisado na próxima seção.

A seguir serão detalhados alguns processos de build automáticos mais utilizados no mercado.

Make

O Make é a ferramenta de linha de comando original para o processo de build. Compila executáveis baseando-se em regras, dependências e comandos.

A implementação do Make pela GNU é incluída na maioria das distribuições Linux e ainda é muito utilizada para compilar programas C e C++. A maioria das distribuições Linux, das ferramentas e utilitários que incluem o código fonte frequentemente incluem um arquivo makefile que possibilita aos usuários realizarem um build do código para criarem o executável. Existe também a versão para Windows.

A seguir temos um exemplo de um comando para compilar três classes em C++:

g++ -o olateste1eteste2 olamundo.cpp teste1.cpp teste2.cpp

Para utilizar o Make em grandes projetos é preciso criar um makefile por projeto, que é simplesmente um arquivo texto que segue algumas regras de formação especial. O arquivo lista os "targets" para serem construídos e as dependências de cada "target". Isto economiza uma grande quantidade de tempo quando o projeto tem centenas de arquivos e apenas alguns precisam ser compilados.

Segue na Listagem 3 um exemplo de um makefile.

Listagem 3. Exemplo de arquivo makefile.


  CC=g++
  OBJDEPS = olamundo.o teste1.o teste2.o
  all: olateste1eteste2
  olateste1eteste2: $(OBJDEPS)
           $(CC) -o olateste1eteste2 $(OBJDEPS)
  olamundo.o: olamundo.cpp
           $(CC) -c olamundo.cpp
  teste1.o: teste1.cpp teste1.h
           $(CC) -c teste1.cpp
  teste2.o: teste2.cpp teste2.h
           $(CC) -c teste2.cpp
  clean:
           rm *.o olateste1eteste2

Uma regra consiste de três partes:

  • Os targets estão do lado esquerdo do dois pontos (:);
  • As dependências estão no lado direito;
  • O comando, que para executar está na linha abaixo, precedido por um tab.

Por exemplo, o clean da listagem anterior não possui dependências e o seu comando remove todos arquivos que terminam com ".o" e qualquer arquivo olateste1eteste2. Também foi utilizada a macro $(CC) que é o mesmo que g++.

Para compilar basta digitar "make" no prompt de comando, então, por padrão, a ferramenta make procura por um arquivo com o nome "makefile" no diretório atual.

Pode-se notar que executar um “make” na linha de comando é muito mais simples do que lembrar de todo o comando necessário para compilar o código. O arquivo makefile é escrito apenas uma vez e reutilizado diversas vezes.

Ant

O Ant é o padrão oficial para compilar projetos Java. Assim como o Make, ele trabalha com targets, dependências e comandos, porém a sintaxe é completamente diferente do Make.

Além disso, o Ant utiliza um documento no padrão XML para cada projeto e elementos XML para listar comandos.

Segue na Listagem 4 um exemplo de um arquivo Ant.

Listagem 4. Exemplo de um arquivo Ant.


  <?xml version="1.0"?>
           <project name="TesteJavaAnt" default="build">
           <property name="src.dir" location="src" />
           <property name="build.dir" location="build/classes" />
           <target name="mkdir">
                     <mkdir dir="${build.dir}" />
                     <mkdir dir="${build.dir}/JARs" />
           </target>
           <target name="build" depends="mkdir">
                     <javac srcdir="${src.dir}" destdir="${build.dir}"/>
           </target>
           <target name="gerajar" depends="build">
                     <jar destfile="${build.dir}/JARs" basedir="${build.dir}"
           </target>
  </project>

Este arquivo é nomeado como build.xml e deve ser colocado na raiz do projeto. Neste exemplo primeiramente será executada a tag mkdir, depois build, que depende de mkdir e, por fim, gerajar, que depende da execução do build.

Para invocar o documento basta digitar "ant" na linha de comando.

O arquivo apresentado mostra diversas propriedades, targets, dependências e comandos que podem ser utilizados no arquivo XML do Ant, onde as propriedades são definidas no topo do arquivo, mas também podem estar em um arquivo separado e ser importado por outro arquivo através da task <import>.

Maven

Embora o Maven esteja substituindo o Ant em diversos projetos, ele é considerado mais do que uma ferramenta de build, pois é conhecido como Project Object Model (Modelo de Objeto de Projeto) ou POM.

Ele assume que os diretórios existem em localizações específicas e uma série de eventos é realizada em sequência (build e após isso deploy, por exemplo).

Para fazer um contraponto com o Ant, no arquivo anterior do Ant havia configurações explícitas de configurações de diretórios como “src.dir” e “build.dir”. No entanto, o Maven, diferentemente do Ant, espera por padrão que o código fonte esteja em “project_dir/src/main/java”, sendo que “project_dir” é o diretório principal do projeto, que as classes compiladas irão para o diretório “project_dir/target/classes” e, por fim, o arquivo JAR seja criado em “project_dir/target”.

Segue na Listagem 5 um exemplo de um arquivo pom.xml.

Listagem 5. Exemplo de um arquivo POM.


  1. <project>
   
  2.   <modelVersion>4.0.0</modelVersion>
  3.   <groupId>br.com.devmedia.ProjetoMaven</groupID>
  4.   <artifactId>TesteMaven</artifactId>
  5.   <version>2.0.0</version>
  6.   <description>Exemplo de um Projeto Maven</description>
  7. </project>

Para o Maven executar o build do projeto basta digitar "mvn install" na linha de comando no diretório onde o arquivo pom.xml está localizado.

Veja que na segunda linha modelVersion indica a versão do modelo do POM, que o arquivo está referenciando. Na linha seguinte, o groupId é o identificador único para a organização que está criando o projeto. Na linha 4 temos o artifactId, que é o identificador único do resultado do arquivo POM e que pode ser o nome do arquivo JAR. Na linha posterior temos o version, que é a versão do artefato produzido, e posteriormente o description, que é uma descrição simples do projeto e que é utilizado normalmente como documentação.

Um arquivo POM pode conter também seções adicionais para dependências, pacotes, exclusões, plugins e muito mais.

Segue na Listagem 6 outro arquivo POM com diversas outras seções e dependências.

Listagem 6. Exemplo de um arquivo POM com dependências.


  <project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>br.com.devmedia.ProjetoMaven</groupID>
    <artifactId>TesteMaven</artifactId>
    <packaging>jar</packaging>
    <name>Maven Completo</name>
    <version>2.0.0</version>
    <dependencies>
      <dependency>
           <groupId>log4j</groupId>
           <artifactId>log4j</artifactId>
           <version>1.2.9</version>
           <scope>compile</scope>
      </dependency>
     </dependencies>
     <description>Exemplo de um Projeto Maven com Dependências</description>
  </project>

A seção <dependencies> lista as bibliotecas externas que o projeto necessita. No exemplo da Listagem 6, o projeto precisa da biblioteca externa “log4j”, que é utilizado pela aplicação para gerar logs. Isso indica para o Maven que o projeto precisa dessa biblioteca para compilar o projeto.

Quando ele encontra uma dependência no arquivo, primeiramente verifica se o repositório local possui essa biblioteca. O repositório local do Maven armazena artefatos que são referenciados frequentemente. Esses arquivos e recursos são mantidos ambos localmente para melhorar a velocidade de compilação e reduzir a sobrecarga na rede. Porém, se o recurso desejado não é encontrado no repositório local, um repositório remoto será pesquisado. Por padrão, o Maven utiliza o seu repositório central localizado em “http://repo.maven.apache.org/maven2/”, mas outros mirrors também podem ser especificados. Para pesquisar recursos disponíveis no repositório do Maven basta visitar a URL da seção Links.

As organizações normalmente configuram um repositório interno que copia a estrutura oficial do repositório do Maven. Isso permite que os desenvolvedores referenciem um repositório na intranet que limita o tráfego externo. Outra vantagem disso é que os desenvolvedores podem disponibilizar bibliotecas para uso interno sem precisar externalizar esses componentes para o público em geral.

Segue na Listagem 7 outro exemplo de um arquivo POM mais completo.

Listagem 7. Exemplo de um arquivo POM com múltiplas dependências e configuração de repositório local.


  <project>
           <modelVersion>4.0.0</modelVersion>
           <groupId>br.com.devmedia.ProjetoMaven</groupID>
           <artifactId>TesteMaven</artifactId>
           <packaging>jar</packaging>
           <name>Maven Completo</name>
           <version>2.0.0</version>
           <repositories>
                     <id>repositorio-interno</id>
                     <name>Repositório Interno da Empresa</name>
                     <url>http://intranet.devmedia/repo</url>
           </repositories>
           <dependencies>
                     <dependency>
                              <groupId>log4j</groupId>
                              <artifactId>log4j</artifactId>
                              <version>1.2.9</version>
                              <scope>compile</scope>
                     </dependency>
                     <dependency>
                              <groupId>junit</groupId>
                              <artifactId>junit</artifactId>
                              <version>4.10</version>
                              <scope>test</scope>
                     </dependency>
                     <dependency>
                              <groupId>br.com.devemedia-url</groupId>
                              <artifactId>customLibrary</artifactId>
                              <version>1.6.0</version>
                              <scope>compile</scope>
                     </dependency>
           </dependencies>
           <description>Exemplo de um Projeto Maven</description>
  </project>


No exemplo apresentado duas bibliotecas são adicionadas, uma biblioteca de um repositório interno (customLibrary) e a outra (JUnit) de um repositório central. Para encontrar o groupId, artifactId, e version, o repositório deve ser consultado para encontrar essa informação.

O Maven procura nos repositórios centrais e no repositório local, visto que o componente não possui nenhum repositório especificado para ele. Se o recurso é necessário para compilação e não for encontrado o build irá falhar.

Integração Contínua

A integração contínua até pouco tempo era realizada de forma manual pelas equipes de desenvolvimento. Basicamente existia um script batch que frequentemente verificava o repositório (podendo ser um SVN, CVS ou GIT) para encontrar alguma alteração no código fonte dos arquivos desde a última compilação realizada. Se fosse encontrada alguma alteração os testes eram novamente realizados para todo o projeto e se os testes fossem executados com sucesso era realizado um build e um deploy em algum ambiente. Por fim, era enviado um e-mail aos desenvolvedores informando se tudo foi realizado com sucesso. A parte de build era realizado chamando um arquivo Ant que poderia já possuir as tasks para o teste unitário, além disso o próprio Ant também realizavam um deploy. A busca por código-fonte alterado no repositório poderia ser feita utilizando a própria ferramenta de versionamento e o e-mail enviado seria realizado através de uma biblioteca do script para enviar e-mail.

Além do batch não ser muito robusto surgiram diversas ferramentas especializadas em integração contínua para suprir essa necessidade. Nas próprias seções veremos algumas delas que estão sendo bastante utilizadas nas organizações.

CruiseControl

O CruiseControl é uma das ferramentas de integração contínua mais utilizadas pelas organizações sendo criada pelos funcionários de uma das empresas mais respeitadas do mundo, a ThoughtWorks, e além de tudo a ferramenta é open source.

A ferramenta inclui dezenas de plugins para uma grande variedade de funcionalidades como controle de código-fonte, tecnologias de build, notificações incluindo e-mail e mensagens instantâneas, entre outras funcionalidades. A interface Web do CruiseControl ainda inclui uma variedade de detalhes sobre os builds realizados.

Segue na Figura 3 uma imagem do CruiseControl.

Tela principal do CruiseControl

Figura 3. Tela principal do CruiseControl.

Como pode-se verificar o CruiseControl possui no menu direito os últimos builds realizados, quais builds executaram com sucesso e quais falharam. No centro da tela o CruiseControl mostra mais informações sobre os builds, mostrando tudo que foi alterado, logs, erros, como foi a execução dos testes, entre outras informações.

Apesar de ter sido escrito em Java o CruiseControl pode ser utilizado em uma grande variedade de projetos.

Entre as ferramentas de build suportadas pelo CruiseControl incluem o Ant e o Maven.

Jenkins

O Jenkins é uma ferramenta de integração contínua open source que realiza rapidamente tarefas que são demoradas como compilação do projeto, execução dos testes automatizados e alerta aos desenvolvedores a cada mudança de código no repositório ou em caso de erros ou falhas nos testes automatizados.

A história do Jenkins se cruza com o Hudson, pois o Jenkins foi originalmente desenvolvido como o projeto Hudson. A criação do Hudson começou em 2004 pela Sun Microsystems e foi lançada em 2005. Por volta de 2007 o Hudson se tornou conhecido por ser uma alterativa melhor que o CruiseControl e outras ferramentas de integração contínua, inclusive ganhando prêmios internacionais. Em 2010 começaram a surgir conflitos com a Oracle que culminou com a separação do projeto e da Oracle. Dessa forma, a Oracle ficou com o nome "Hudson" e em 2011 o antigo Hudson ganhou o nome de Jenkins. Assim, atualmente os dois projetos convivem independentemente um do outro com seus respectivos desenvolvedores e membros.

O Hudson foi desenvolvido através de um fork do Jenkins, por isso a semelhança entre os dois. Esta ferramenta será mais bem detalhada na próxima seção.

Como o Jenkins é distribuído como um war basta fazer um deploy em um servidor de aplicação como o Tomcat sendo acessível através da URL http://localhost:8080/jenkins/ e sua tela principal é apresentado na Figura 4.

Tela principal do Jenkins

Figura 4. Tela principal do Jenkins.

O Jenkins permite a instalação de plugins através do Plugin Manager, criação de tasks para compilar e executar testes no projeto, verificação de alterações do código-fonte através da configuração de um controle de versão, configuração de e-mail para notificações, entre outras funcionalidades.

Hudson

O Hudson é outra ferramenta de integração contínua muito utilizada no ambiente Java para automatizar builds e testes automatizados. Assim, ao encontrar uma alteração no repositório do SVN ou GIT o Hudson executa o build e testa automaticamente o projeto.

A ferramenta é fácil de ser operada e a instalação é bastante simples. Assim como o Jenkins o Hudson é um arquivo war que precisa ser implantada em um container web como o Tomcat.

Segue na Figura 5 a tela principal do Hudson.

Tela principal do Hudson

Figura 5. Tela principal do Hudson.

O Hudson também inclui diversos plugins como PMD, JUnit e outras ferramentas que ampliam as capacidades da ferramenta.

Por fim, o Hudson também possui um bom sistema de controle de usuários que podem acessar a aplicação.

Analisando o número de versões lançadas e o número de membros e organizações que utilizam o Jenkins e o Hudson, conclui-se que o primeiro é a melhor opção, mais atualizada e utilizada.

Microsoft Team Foundation Server

O Microsoft Team Foundation Server é uma ferramenta de integração contínua da Microsoft que oferece diversos produtos como gerenciamento de código-fonte, relatórios, requisitos, gerenciamento de projeto, builds automatizados, teste, entre outros. Esta ferramenta suporta todo o ciclo de vida de uma aplicação.

Além de ser integrada no Visual Studio o Microsoft Team Foundation Server também pode ser integrado no Eclipse.

Comparação das Ferramentas

Na Tabela 1 foram reunidas algumas características das principais ferramentas de integração continua mais utilizadas.

Ferramenta

Plataforma

Proprietária

Ferramenta de Build (Windows)

Ferramenta de Build (Java)

Notificação

Integração com IDE

AnthillPro

Multiplataforma

Sim

MSBuild, NAnt, Visual Studio

Ant, Maven

E-mail, XMPP/Jabber, RSS, Systray

Diversas

Apache Continuum

Servlet Container

Não

Nenhum

Maven

Mail, Jabber, Google Talk, MSN, IRC, Wagon

Nenhuma

CruiseControl

Multiplataforma

Não

NAnt, Rake, e Xcode

Phing, Apache Ant, Maven

E-mail, CCTray

Eclipse

Jenkins/Hudson

Multiplataforma

Não

MSBuild, NAnt

Ant, Maven 2, Kundo

Android, E-mail, Google Calendar, IRC, XMPP, RSS, Twitter

Eclipse, IntelliJ IDEA, NetBeans

Team Foundation Server

Windows

Sim

MSBuild

Ant, Maven

E-Mail, SOAP

Visual Studio, Eclipse

Tabela 1. Comparação das principais características das ferramentas de integração.

Bibliografia

[1] Manual do Ant
http://ant.apache.org/manual

[2] Manual do CruiseControl
http://cruisecontrol.sourceforge.net/

[3] Edward Crookshanks. Practical Software Development Techniques: Tools and Techniques for Building Enterprise Software. Apress, 2014.

[4] Repositório Mavem
http://search.maven.org/#browse