Introdução a Jbehave

Julho 22, 2012

Neste post vamos falar um pouco sobre desenvolvimento orientado a comportamento (Behaviour Driven Development) também conhecido como BDD e focar em uma das ferramentas que facilitam a execução dessa prática, o jbehave.

As ferramentas utilizadas são Maven 3, jbehave (v 3.6.8) e o jUnit (4.10). O código-fonte do projeto está disponível no github.

Introdução

O desenvolvimento orientado a comportamento é uma evolução do desenvolvimento orientado a testes (Test Driven Development) ou TDD. Falamos um pouco sobre TDD em um post anterior.

Uma das motivações para a reformulação da prática foi o fato de que muitos adeptos não percebiam que TDD é mais sobre definição de comportamento do que sobre testes propriamente dito.

O BDD visa minimizar falhas de comunicação entre o pessoal de negócio e o pessoal técnico. Para isso, se baseia em um vocabulário comum e conciso para garantir que todos usem os mesmos termos, utilizando em geral o projeto orientado a domínio (Domain Driven Design).

Os testes definem comportamentos, ou seja, a relação entre os domínios do sistema. A ideia é que com o uso de um vocabulário comum, os testes sirvam como documentação das especificações do sistema, de modo que possam ser lidos pelos desenvolvedores, testadores e analistas de negócio.

Algumas ferramentas auxiliam nesse processo, permitindo a descrição dos testes em linguagem natural. Algumas dessas ferramentas para trabalhar com BDD em Java são o jbehave, o Concordion e o easyb (que na verdade usa Groovy). Uma outra bastante conhecida é o RSpec, voltada para Ruby.

Jbehave

O jbehave é um framework de desenvolvimento orientado a comportamento ou BDD (Behaviour Driven Development), desenvolvido por Dan North.

http://brianrepko.files.wordpress.com/2010/09/jbehave-logo.png

Exemplo prático

O uso do jbehave consiste em 3 passos principais:

  1. A implementação de uma classe POJO com anotações para criar uma correspondência entre o texto da estória e os métodos dessa classe.
  2. A criação de uma estória (essencialmente um arquivo de texto com extensão .story)
  3. A implementação de um embutidor ou embarcador (traduções livres de embedder) para que as estórias sejam executadas através de um framework de testes, que no nosso caso será o JUnit.

Vamos considerar um exemplo bastante simples: uma classe com um método que soma dois inteiros e guarda o resultado em uma variável interna. Vamos chamá-la de Adder:

public class Adder {

    private int result;
	
    public void add(int a, int b){
        result = a + b;
    }	
    public int getResult(){
        return result;
    }	
}

Implementação do POJO

Vamos criar uma classe que fará a correspondência entre a estória e os métodos de teste. Vamos chamá-la de AdderSteps.

public class AdderSteps {
	
    private Adder adder;
	
    @Given("um somador é criado")
    @Aliases(values={"um somador é instanciado"})
    public void theAdderIsCreated(){
        adder = new Adder();
    }
	
    @When("eu adiciono $a e $b")
    public void iAddTwoNumbers(int a, int b){
        adder.add(a, b);
    }

    @Then("o resultado deve ser $c")
    public void theResultMustBe(int c){
        assertEquals(adder.getResult(), c);
    }

}

O método theAdderIsCreated instancia um Adder, iAddTwoNumbers faz a soma dos dois números através do método do Adder e theResultMustBe verifica se o resultado da soma é igual ao parâmetro (usa o JUnit).

O principal destaque do código acima são as anotações Given, Aliases, When e Then.

A anotação Given faz a associação com a linha começando com a palavra-chave given no arquivo de texto e continuando com o texto passado como parâmetro. Assim, se houver a linha no arquivo de texto da estória

Given um somador é criado

O método theAdderIsCreated() será chamado. O uso da anotação Aliases permite que o método seja chamado com outros nomes. No caso a linha abaixo

Given um somador é instanciado

também chamaria o método.

As anotações When e Then são análogas para as palavras-chaves when e then. Porém, no exemplo vemos que as strings de parâmetro dessas anotações têm o símbolo $.

Isso faz a associação das variáveis com os valores presentes no arquivo de estória. Por exemplo, a linha

When eu adiciono 1 e 2

associará a variável a com 1 e a variável b com 2, chamando o método iAddTwoNumbers(1, 2).

Definição de uma estória

Com a classe que faz as correspondências implementada, podemos criar um caso de teste através de uma estória:

Given um somador é criado
When eu adiciono 1 e 4
Then o resultado deve ser 5

Veja como o teste fica bem mais legível nessa forma do em código, servindo como uma documentação natural.

Se as especificações são dadas em português então faz sentido o texto também ser escrito assim. Entretanto, o uso de palavras-chaves em inglês deixa o texto estranho. A seguir veremos como contornar este problema.

Implementação do embutidor para o JUnit

Agora podemos embutir nossa estória em um framework de testes, no caso o JUnit. O jbehave define uma classe própria para isso chamada JUnitStory. Agora basta estendê-la e sobrescrever o método stepsFactory(), para instanciar a nossa classe de associação, o AdderSteps.

public class TestAdder extends JUnitStory {
 
    @Override
    public InjectableStepsFactory stepsFactory() {        
        return new InstanceStepsFactory(configuration(), new AdderSteps());
    }
}

O método configuration() retorna um objeto Configuration contendo diversas configurações. Para personalizá-las, podemos sobrescrever o método configuration():

@Override
public Configuration configuration() {
    return new MostUsefulConfiguration()
        // Onde procurar pelas estórias
        .useStoryLoader(new LoadFromClasspath(this.getClass()))
        // Para onde fazer os reports
        .useStoryReporterBuilder(new StoryReporterBuilder()
                                 .withDefaultFormats()
                                 .withFormats(Format.CONSOLE, Format.HTML)); 
}

A classe Configuration usa o encadeamento de método para obter uma interface mais fluente.

O método MostUsefulConfiguration() instancia uma configuração padrão e o useStoryLoader() personaliza o modo como as estórias são carregadas. Neste caso, estamos especificando que ele fica no mesmo caminho da classe.

A classe está no pacote me.kuniga.jbehave e por convenção fica em test/java/me/kuniga/jbehave. Portanto, podemos colocar as estórias em test/resources/me/kuniga/jbehave que o maven tratará de colocá-las no mesmo caminho.

O método useStoryReporterBuilder() configura como os resultados do teste serão exibidos. No código acima estamos indicando que tais resultados serão exibidos na saída padrão e exportados para html, que por padrão ficam em target/jbehave/

A classe JUnitStory possui um método anotado com @Test e por isso nossa classe TestAdder será executada pelo JUnit.

Nota: Até o momento não consegui rodar os testes através do maven devido a problemas de encoding (a associação com os métodos não é feita corretamente). Se eu descobrir o porquê disso edito o post. Por ora, é possível rodar os testes pelo próprio Eclipse.

Ao executarmos os testes, um relatório estará disponível em target/jbehave/me.kuniga.jbehave.test_adder.html.

Palavras-chaves em Português

É possível configurar o JBehave para reconheça as palavras-chaves traduzidas. Primeiramente devemos instanciar um objeto da classe Keywords com o idioma português. Felizmente o JBehave já provê um arquivo de propriedades com as traduções. Portanto, basta fazermos:

Keywords keywords = new LocalizedKeywords(new Locale("pt"));

As traduções para as palavras-chaves são: “Given” – “Dado que“; “When” – “Quando“; “Then” – “Então“.

Então configuramos as palavras-chave e o parser das estórias através dos métodos useKeywords() e useStoryParser() [2].

O método configuration() completo fica:

public Configuration configuration() {
    	
    Keywords keywords = new LocalizedKeywords(new Locale("pt"));
    	
    return new MostUsefulConfiguration()
        .useKeywords(keywords)
        .useStoryParser(new RegexStoryParser(keywords))
        // Onde procurar pelas estórias
        .useStoryLoader(new LoadFromClasspath(this.getClass()))
        // Para onde fazer os reports
        .useStoryReporterBuilder(new StoryReporterBuilder()
                                 .withDefaultFormats()
                                 .withFormats(Format.CONSOLE, Format.HTML)); 
}

Agora podemos re-escrever nossa estória como:

Dado que um somador é instanciado
Quando eu adiciono 1 e 4
Então o resultado deve ser 5

Código-fonte do JBehave

O código-fonte do jBehave vem com alguns exemplos. Pode-se obter o código usando git:

git clone git://git.codehaus.org/jbehave-core.git

Os exemplos se encontram em jbehave-core/examples/.

O arquivo de propriedades com a tradução das palavras-chaves além de outros termos pode ser encontrado em:

jbehave-core/jbehave-core/src/main/resources/i18n/keywords_pt.properties

Conclusão

Achei muito bacana a ideia de gerar o código a partir de uma descrição textual, principalmente em casos em que gerar um caso de teste exige muita configuração.

Além do mais, o teste fica naturalmente documentado e essa documentação fica atrelada ao código, não correndo o risco de ficar ultrapassada (princípio DRY).

Para criar instâncias complexas é interessante o uso do encadeamento de métodos sobre o qual falamos acima. Cada método de configuração poderia ser associado a um @Given. Combinando com as ideias de [3], poderíamos especificar um linguagem de domínio específico (Domain Specific Language ou DSL) para testes usando o jbehave!

Referências

[1] Behaviour-driven.org – Introduction
[2] jbehave – Stories in your language
[3] The Java Fluent API Designer Crash Course


Testes Unitários em Java

Janeiro 8, 2012

Hoje vou discutir um pouco sobre testes unitários em Java, especificamente usando os frameworks JUnit e EasyMock.

Introdução

Basicamente, testes unitários visam garantir que as partes de um programa estão corretas, da maneira mais independente possível. Isso em geral é feito através de assertivas, que verificam se as saídas desses componentes respeitam condições pré-estabelecidas.

Testes unitários são bastante utilizados em uma metodologia de desenvolvimento chamada Test Driven Development (TDD). A ideia básica dessa metodologia é, antes de implementar uma funcionalidade, escrever testes que a verifiquem e então implementar o código mais simples que passe em todos os testes. Os testes criados são geralmente testes unitários automatizados e que devem ser executados a cada nova funcionalidade implementada.

Em Java, há um framework chamado JUnit, que auxilia na criação e execução/automação desse tipo de teste. Já o EasyMock, é um complemento de frameworks como o JUnit e pode ser utilizado para diminuir a dependência entre diferentes partes do código.

JUnit

Usar o JUnit em sua forma mais básica é bem simples. A maior parte da configuração é feita através de anotações. Métodos anotados com @Test são invocados pela classe org.junit.runner.JUnitCore.

A principal vantagem do JUnit é sua integração com diversas ferramentas, como por exemplo o eclipse e o maven. O eclipse provê interfaces para a criação de novas classes de testes e a execução dos testes usando o JUnit. Para mais informações sobre a integração com o eclipse, este tutorial [1] é excelente.

O maven, por sua parte, usa na sua convenção de diretórios o src/test, onde devem ser colocados os fontes de testes. Além disso, à fase test está associado o plugin surefire, que roda os testes através do JUnit.

Note que como a fase test é anterior à fase package ou install, na maior parte das vezes em que se faz o build do código, os métodos anotados com @Test são automaticamente executados.

Outra observação é que por padrão, o plugin só considera as classes com os nomes dentro de um desses padrões: **/*Test.java, **/Test*.java ou **/*TestCase.java. Eu particularmente não entendo porque o padrão não inclui também todos os testes do diretório src/test/java/. Enfim, é possível incluir ou excluir classes explicitamente, conforme descrito aqui.

A dependência do JUnit (no meu caso, a versão 4.10) deve ser incluída no arquivo POM:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.10</version>
  <scope>test</scope>
</dependency>

Para pular os testes, principalmente se estes forem muito demorados, pode-se passar o parâmetro
-Dmaven.test.skip=true para o maven.

EasyMock

Dado o princípio de testes unitários de só testar um dado método ou uma função, é importante que isolemos esse pedaço de código de dependências externas. Para isso, podemos usar mocks, que visam reproduzir o comportamento da classe externa.

O framework EasyMock fornece uma maneira simples de criar esses mocks. Dada uma interface java, podemos criar um mock que “implementa” essa interface. Podemos então especificar, para cada método, qual o valor esperado para uma dada entrada.

Como um exemplo, podemos definir uma interface que provê dados de entrada. Para o código que vai usá-la não importa se os dados virão de uma base de dados, arquivo de texto, entrada padrão, etc. A restrição é que os métodos especificados pela interface sejam definidos.

public interface IDataReader {    
    enum Mode {
        MODE1,
        MODE2
    }
    int getValue(Mode mode);
}

Na nossa interface de exemplo, temos um método que retorna um valor de acordo com um modo.

Para facilitar os testes unitários, podemos usar o design pattern chamado injeção de dependência (dependency injection). Nesse padrão, temos um consumidor que faz uso da interface de maneira transparente e o injetor que escolhe a implementação dessa interface.

A ideia é que o injetor fique fora do consumidor, para que possamos controlá-lo sem mexer em seu código. Uma possibilidade é manter uma referência interna para a classe que provê a implementação, e que possamos setar esse membro externamente. Por exemplo, vamos criar a seguinte classe:

public class App {

    private IDataReader dataReader;
	
    public App(IDataReader dataReader){
        this.dataReader = dataReader;
    }
}

Note que passamos um objeto de uma classe que implementa IDataReader pelo construtor. Dessa forma, ao criar uma nova implementação para IDataReader (no nosso caso será o mock) não precisamos mexer em uma linha sequer do código de App.java.

Vamos adicionar o método mutiply, que fará uso da interface IDataReader. O código completo da classe App fica:

public class App 
{
    private IDataReader dataReader;
    
    public App(IDataReader dataReader){
        this.dataReader = dataReader;
    }
    public int multiply(){
        int value1 = dataReader.getValue(Mode.MODE1);
        int value2 = dataReader.getValue(Mode.MODE2);
        return value1 * value2;
    }
}

Neste exemplo de brinquedo, multiplicamos os resultados das chamadas do método getValue para cada um dos dois modos. Agora vamos criar a classe pare testar o método multiply.

public class AppTest {

    private IDataReader mockReader;
	
    @Before
    public void setup(){
        mockReader = EasyMock.createMock(IDataReader.class);
    }
	
    @Test
    public void testApp(){
				
        EasyMock.expect(mockReader.getValue(Mode.MODE1)).andReturn(6);
        EasyMock.expect(mockReader.getValue(Mode.MODE2)).andReturn(7);
		
        EasyMock.replay(mockReader);
		
        App tester = new App(mockReader);
		
        assertEquals("Result", 42, tester.multiply());
    }
}

No código acima, o membro mockReader contém uma referência para a implementação mock da interface IDataReader. O método setup anotado com @Before, é chamado antes de cada teste rodar e consiste em criar uma instância mock.

A sintaxe da função estática expect é meio estranha, mas podemos ler da seguinte maneira: estamos dizendo que quando o método getValue() for chamado com o parâmetro mode.MODE1, ele retornará 6, enquanto que com o parâmetro mode.MODE2() retornará 7.

Além disso, por padrão supõe-se que o método será chamado apenas uma vez. Se a chamada de tester.mutiply implica na chamada de getValue1 (getValue2) mais de uma vez, o teste apontará um erro. É possível especificar o número de vezes que o método será chamado, com o método times:

EasyMock.expect(mockReader.getValue(mode.MODE1)).andReturn(6).times(4);

Nesse caso o método deverá ser chamado no máximo 4 vezes. Se quisermos relaxar a condição de contagem de uso, podemos usar métodos como times(int min, int max), atLeastOnce() e anyTimes() [4].

O método estático replay serve para consolidar as definições feitas com o expect. Finalmente a implementação mock é passada como parâmetro para o construtor da classe App e podemos verificar se a multiplicação está se comportando como o esperado.

Para incluir o easymock como dependência do maven, basta incluir:

<dependency>
  <groupId>org.easymock</groupId>
  <artifactId>easymock</artifactId>
  <version>3.1</version>
</dependency>

Referências

[1] http://www.vogella.de/articles/JUnit/article.html
[2] Wikipedia – Unit Testing
[3] Wikipedia – Dependency Injection
[4] Easy Mock Documentation
[5] Easy Mock Java Docs
[6] http://www.vogella.de/articles/EasyMock/article.html


Java Servlets no Tomcat

Dezembro 11, 2011

Nesse post vou comentar um pouco sobre Servlets e um exemplo básico de utilização usando o Tomcat.

O que são Servlets?

Segundo a Wikipedia, um Servlet é uma classe Java usada para estender a funcionalidade de um servidor, sendo capaz de receber e enviar mensagens via um protocolo cliente-servidor (em geral HTTP).

Aplicações que utilizam a API de Servlets podem ser disponibilizadas na web através de um Web Container. Há diversos Web Container’s grátis, como por exemplo, o GlassFish (da própria Oracle), o JBoss AS (da Red Hat) e o Tomcat (da Apache). Podemos pensar em Web Conteiner’s como bibliotecas que implementam a API de Servlets.

A versão da API de Servlets que vamos utilizar é a 3.0 (Java EE6). Essa versão é suportada pelas seguintes versões dos Web Container’s citados acima: GlasshFish +3, JBoss AS +7 e Tomcat +7. Em nosso exemplo usaremos o Tomcat 7.

O build e o deploy do código serão feitos através do Maven.

Tomcat 7

Antes de mais nada, vamos instalar e configurar o Tomcat 7. Para instalar, basta baixar os .jar diretamente do site do Tomcat em um diretório qualquer. A partir de agora vamos nos referir ao local de instalação como $TOMCAT_DIR. Na pasta $TOMCAT_DIR/bin, podemos iniciar o Tomcat através do script startup.sh.

Para verificar se o Tomcat foi corretamente iniciado, podemos entrar no endereço localhost:8080.

Podemos gerenciar o Tomcat via web, mas para isso, precisamos adicionar um usuário no arquivo $TOMCAT_DIR/conf/tomcat-users.xml. Nesse arquivo xml, adicione a seguinte entrada dentro da tag <tomcat-users>:

<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<user username="nome-usuario" password="senha" roles="manager-gui, manager-script"/>

trocando "nome-usuario"/"senha" por valores adequados. É preciso reiniciar o Tomcat (shutdown.sh + startup.sh). Agora você deverá poder acessar a página localhost:8080/manager com o usuário e senhas cadastrados.

Servlet Java

A estrutura de diretórios do nosso exemplo é a seguinte:

|-- pom.xml
 `-- src
     `-- main
         |-- java
         |   `-- me
         |       `-- kuniga
         |           `-- main
         |               `-- MyApp.java
         |-- resources
         |
         `-- webapp
             `-- WEB-INF

A implementação da nossa única classe é baseada em [4]. Uma das novidades da API de Servlets 3.0 é o uso de anotações para configuração, como podemos ver no código a seguir:

@WebServlet("/MyApp")
public class MyApp extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest request,
                       HttpServletResponse response)
      throws ServletException, IOException {

    String nome = request.getParameter("nome");

    PrintWriter out = response.getWriter();
    out.println("Ola: " + nome);
  }
}

O argumento passado para a anotação WebServlet definirá a url de acesso à nossa aplicação. Já o método doGet será invocado quando uma requisição do tipo GET for feita. O conteúdo passado para o PrintWriter será retornado para o cliente.

Para compilar o código acima, devemos incluir a API 3.0 de Servlets. Podemos fazer isso incluindo uma dependência das APIs do Java EE6:

<dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-api</artifactId>
  <version>6.0</version>
  <scope>provided</scope>
</dependency>

É importante setar o escopo como provided para que o maven não coloque o .jar contendo as APIs do Java EE6 no arquivo .war. Isso porque no Tomcat esse .jar já está presente e a duplicata ocasiona erro. (Aliás, uma dica para debugar erros é iniciar o Tomcat através do comando ./catalina.sh run ao invés do ./startup.sh, pois desta forma as mensagens de log são exibidas no terminal).

Configurando plugins do Maven

Basicamente o que o maven fará é compilar o programa e compactá-lo em um arquivo .war e então fará o deploy desse arquivo no Tomcat.

Vamos primeiramente configurar o plugin maven-war-plugin, que é responsável por gerar o arquivo .war. Em versões anteriores da API de Servlets, era necessário um arquivo de configuração chamado web.xml que ia em webapp/WEB-INF. Agora, a configuração básica pode ser feita através de anotações na classe do servlet e o arquivo web.xml não é obrigatório.

Por padrão, o plugin maven-war-plugin gera um erro na ausência do web.xml, mas podemos modificar esse comportamento através da seguinte configuração no pom.xml:

<plugin>
  <artifactId>maven-war-plugin</artifactId>
  <configuration>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </configuration>
</plugin>

O outro plugin que iremos configurar é o tomcat-maven-plugin, responsável pelo deploy no tomcat. Para isso, ele precisa da url de acesso ao manager, bem como um login válido para acessá-la. Podemos passar os dados de login através do pom.xml.

Mais especificamente, adicionamos uma entrada no settings.xml do maven, que se encontra em ~/.m2/settings.xml (mais informações sobre esse arquivo aqui).

<server>
  <id>mytomcat</id>
  <username>nome-usuario</username>
  <password>senha</password>
</server>

Onde id é um identificador qualquer, e username e password são aqueles que usamos no cadastro de usuário no Tomcat. No pom.xml do projeto passamos essa informação de login e a url da página:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>tomcat-maven-plugin</artifactId>
  <configuration>
    <url>http://localhost:8080/manager/html</url>
    <server>mytomcat</server>
  </configuration>
</plugin>

A versão completa do pom.xml está aqui e também disponibilizei o projeto todo no github.

Agora podemos executar os seguintes goals do maven:

> mvn clean package tomcat:deploy

Rodando a aplicação

Para testar a aplicação em funcionamento, podemos acessar a seguinte url: http://localhost:8080/kservlet/MyApp?nome=mundo

Referências

[1] Java Servlet – Página oficial da Oracle
[2] Java Servlet – Wikipedia
[3] How do I deploy a maven web application to Tomcat?
[4] Blog Caelum – Java EE6: Começando com as Servlets 3.0
[5] Tomcat 6.0.32 + Maven undeploy via script not working


Maven

Outubro 2, 2011

Uma ferramenta que conheci recentemente é o apache maven, que serve para gerenciar projetos java, como por exemplo compilar código java, gerar documentação, fazer deploy de aplicações web etc.

O objetivo desse post é cobrir os principais conceitos dessa ferramenta, apontando referências para quem quiser se aprofundar mais. Me baseei na versão mais recente do maven, o maven 3 (versão 3.0.3 mais especificamente).

O arquivo pom.xml

O maven usa um arquivo de configuração chamado Project Object Model (POM) para determinar as características do projeto e suas dependências.

Maven usa o paradigma de convenção ao invés de configuração. Isso quer dizer que, a menos que explicitado de outra forma, ele supõe configurações padrões (como por exemplo a localização dos códigos-fontes), o que permite que o arquivo pom.xml fique bem pequeno.

Um exemplo de um arquivo pom.xml é o seguinte:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>br.com.organizacao</groupId>
  <artifactId>teste</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
</project>

No exemplo acima, modelVersion define a versão da estrutura POM utilizada e a versão atualmente usada pelo maven é a 4.0.0. Segundo o blog da Caelum [2], os seguintes parâmetros são suficientes para identificar unicamente um projeto:

  • groupId – um identificador da empresa/grupo ao qual o projeto pertence. Geralmente o nome do site da empresa/grupo ao contrário;
  • artifactId – o nome do projeto;
  • version – a versão atual do projeto (PS: o modificador SNAPSHOT indica que essa versão é de desenvolvimento);

Além disso, no livro Maven By Example [1], eles definem o conceito de coordenadas maven, que é um conjunto de informações utilizadas para se referir a um projeto. Além das três propriedades acima, inclue-se o parâmetro packaging, que definirá se o pacote será comprimido em um jar, war, ear, etc. Dessa forma, o projeto acima seria referido por: br.com.organizacao:teste:jar:1.0-SNAPSHOT.

O maven implementa um mecanismo de herança. Isso quer dizer que um dado POM herda as configurações de seus ancestrais podendo sobrescrevê-las. Existe um POM que é ancestral de todos os POMs, o chamado super POM, de onde se herdam as configurações padrões.

Outro mecanismo é o de agregação, onde um POM principal pode definir módulos (sub-projetos) associados a outros POMs, que serão chamados pelo POM principal. Em [4], há um tópico detalhando e fornecendo exemplos para esses mecanismos.

Plugins

Se formos ver, o tamanho do maven é bem pequeno. Isso porque a maioria de suas funcionalidades é provida através de plugins. Um plugin consiste basicamente de uma coleção de goals. A sintaxe para executar um goal é

$mvn <plugin>:<goal>

Um exemplo é o plugin compiler, que possui o goal compile. Esse goal compila o código java e pode ser invocado da seguinte maneira:

$mvn compiler:compile

Repositório central do maven

Tanto os plugins quanto as bibliotecas mais comuns são mantidas em um repositório remoto central, chamado Maven Central Repository. Quando há uma dependência, quer de um plugin ou de uma biblioteca, o maven consulta esse repositório e cuida de fazer o download automaticamente.

Os objetos baixados são armazenados em um repositório local, que fica geralmente no diretório ~/.m2. Quando fazemos o build de um projeto nosso com o maven, ele também é copiado para esse diretório.

Ciclo de Vida do Build

O maven utiliza o conceito de ciclos de vida, que são compostos por uma sequência ordenada de fases. Há três ciclos de vida pré-existentes no maven: clean, default e site. O ciclo de vida default é composto pela sequência de fases: validate, compile, test, package, integration-test, verify, install, deploy (na verdade essa sequência está simplificada – a lista completa está nessa referência). Essas fases são sempre executadas nessa ordem. Assim, se executarmos a fase

$mvn install

Todas as fases que são anteriores a install serão executadas. Se quisermos executar o ciclo de vida default completo, basta executar a última fase, que é o deploy.

Na verdade uma fase não é executada, mas sim os goals associados a elas. Alguns goals já vêm associados por padrão a algumas fases. Por exemplo, ao declararmos o tipo de packaging como jar, o goal compiler:compile é associado à fase compile do ciclo de vida default. Para saber quais goals estão associados a quais fases por padrão, consulte essa referência.

Podemos associar mais goals às fases de build especificando no próprio arquivo pom.xml. Em [5], supõe-se a existência de um plugin chamado display-maven-plugin, que tem o goal time, que imprime o horário atual na saída padrão. Se quisermos imprimir o horário após a fase de testes terem sido executados, podemos associar esse goal à fase test, como no exemplo a seguir:

...
 <plugin>
   <groupId>com.mycompany.example</groupId>
   <artifactId>display-maven-plugin</artifactId>
   <version>1.0</version>
   <executions>
     <execution>
       <phase>test</phase>
       <goals>
         <goal>time</goal>
       </goals>
     </execution>
   </executions>
 </plugin>
...

Note que os conceitos de ciclos de vida e fases são uma maneira de organizar os diversos goals usados no processo de build. Em teoria poderíamos especificá-los explicitamente e estes seriam executados na ordem em que foram passados à linha de comando.

Plugin para o eclipse

Finalmente, para quem usa eclipse, existe um plugin para integração com o maven. A versão do eclipse que estou usando é a 3.7 (Indigo) e o plugin é o m2eclipse.

Para instalar o plugin, vá em Help > Install New Software…

Na barra de endereços coloque: http://download.eclipse.org/technology/m2e/releases

Selecione o item Maven Integration For Eclipse e siga as instruções até concluir a instalação. Algumas funcionalidades que esse plugin oferece são:

Importar um projeto que usa maven, no eclipse. O plugin utiliza informações do pom.xml para isso. Depois de instalado o plugin, vá em:

File > Import… > Maven > Existing Maven Projects

Next, preencha o campo Root directory e então Finish. Pronto, o projeto deve ter sido importado.

Uma alternativa é usar o plugin do próprio maven, o eclipse, para gerar os .classpath e .project. Para isso basta fazer

mvn eclipse:eclipse

Depois é só importar o projeto no eclipse (General > Existing Projects into Workspace).

Fazer o build do projeto dentro do eclipse.

Figura 1: fases do maven providas pelo plugin m2e

Há algumas fases pré-existentes providas pelo plugin, como as da Figura 1. Entretanto, se quisermos personalizar o build, basta ir em:

Run > Run As… > Maven build…

Podemos configurar o build do maven, ajustando os goals ou fases (e.g. clean, package, install, compiler:compile) e definindo opções como modo offline (opção -o), pular testes (opção -Dmaven.test.skip=true), etc.

Para um tutorial mais completo, refira-se a [3], que trata de assuntos como a criação de projetos usando o plugin archetype do maven, importação de projetos (inclusive via checkout de um repositório remoto), etc.

Leitura adicional

[1] Livro Maven by Example
[2] Processo de build com o Maven
[3] Introduction to m2eclipse
[4] Introduction to the POM
[5]Introduction to the Lifecycle