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