BDD com JBehave
Conforme prometido no tópico anterior, irei mostrar o conceito de BDD através da utilização do framework JBehave, que é o framework de BDD mais famoso para Java e que foi desenvolvido pelo Dan North, o idealizador deste conceito.
Antes de mais nada, vamos citar alguns conceitos importantes de BDD.
Estórias e Cenários
BDD é baseado em estórias e cenários, e para criarmos ambos, vamos seguir alguns padrões pré-definidos por autores de BDD. Uma vez que BDD é focado em comportamento e sequência, as estórias e cenários devem ter um formato rígido para que as ferramentas consigam entendê-los e interpretá-los.
As estórias, possuem o seguinte formato:
| Text | | copiar código | | ? |
| 1 | |
| 2 | As I [X] |
| 3 | I want [Y] |
| 4 | so that [Z] |
| 5 |
E os cenários possuem o seguinte formato:
| Text | | copiar código | | ? |
| 1 | |
| 2 | Given some initial context (the givens), |
| 3 | When an event occurs, |
| 4 | then ensure some outcomes. |
| 5 |
Ou seja, isso quer dizer que devemos pensar em nossos requisitos de maneira que possam ser escritos baseados nessa convenção.
O JBehave é originalmente escrito para entender estórias em inglês, entretanto como BDD é focado em pessoas (stakeholders) e na descrição de situações reais, faz muito mais sentido semanticamente que as estórias e cenários sejam escritos em português e para resolver isso, usei uma contribuição do Emerson Macedo para que os cenários possam ser escritos e entendidos em português pelo JBehave.
Começando com o JBehave
O primeiro passo para utilização do JBehave, é fazer download do JAR do projeto e incluí-lo no classpath do seu projeto.
O próximo passo é escrever a estória juntamente com o cenário e para tal resolvi criar um exemplo simples para que o entendimento do conceito possa ser casado através do código a ser desenvolvido.
O exemplo escolhido é baseado no requisito: Compra de produtos em promoção com aviso por email. E é isso que nosso cenário BDD deve descrever.
Por questão de organização, resolvi quebrar a estória em 3 cenários diferentes, onde a primeira parte representa a verificação de produtos em promoção, a segunda representa a compra desses produtos e o terceiro é a finalização da compra e o envio de email. Então, a estória e os cenários ficaram assim:
| Text | | copiar código | | ? |
| 01 | |
| 02 | Compra de produtos em promoção com aviso por email |
| 03 | |
| 04 | Narrativa: |
| 05 | Como um usuário |
| 06 | Eu quero comprar produtos em promoção |
| 07 | E então receber um email de confirmação |
| 08 | |
| 09 | Cenário: Verificar produtos em promoção |
| 10 | Dado Que uma loja possui 10 produtos |
| 11 | E Que 5 estão em promoção |
| 12 | Quando Eu verifico quais estão em promoção |
| 13 | Então Preencho minha sacola apenas com produtos em promoção |
| 14 | |
| 15 | Cenário: Comprar produtos em promoção |
| 16 | Dado Que minha sacola de compras está preenchida com 5 produtos em promoção |
| 17 | Quando Eu verifico o somatório de produtos |
| 18 | Então Devo ter somente um total de 500 reais em produtos |
| 19 | |
| 20 | Cenário: Enviar email |
| 21 | Dado Que minha sacola está preenchida de produtos |
| 22 | Quando Enviar um email de confirmação |
| 23 | Então Devo receber o status do envio "true" |
| 24 |
A primeira linha do arquivo texto representa o título da nossa estória, o requisito de fato. A narrativa é um resumo do que essa estória deve cumprir e a seguir vem os três cenários, que no caso são as três etapas da estória.
É importante notar, que as palavras chaves em inglês (Given, When, Then, And) foram substituídas por palavras em português (Dado, Quando, Então, E).
Esse arquivo texto deve ser salvo com a extensão *.cenario e o seu nome está diretamente ligado a classe que irei criar no passo a seguir, que no caso é a classe que representa a estória e a associação com os cenários.
Criando a classe que irá comportar os cenários
Nesse passo irei criar a classe que irá apontar os cenários que compõe a estória e essa classe deve extender a classe Scenario do JBehave. Dessa forma, a classe ficaria assim:
PrecoPromocaoTeste.java
| Java | | copiar código | | ? |
| 01 | |
| 02 | package scenario1; |
| 03 | |
| 04 | import scenario1.steps.CompraSteps; |
| 05 | import scenario1.steps.EmailSteps; |
| 06 | import scenario1.steps.PromocaoSteps; |
| 07 | import scenario1.util.PtBRScenario; |
| 08 | |
| 09 | public class PrecoPromocaoTeste extends PtBRScenario { |
| 10 | public PrecoPromocaoTeste() { |
| 11 | addSteps(new PromocaoSteps()); |
| 12 | addSteps(new CompraSteps()); |
| 13 | addSteps(new EmailSteps()); |
| 14 | } |
| 15 | } |
| 16 |
No exemplo acima, estou extendo a classe PtBRScenario só para que a estória e o cenário sejam escritos em português, mas essa classa herda a classe Scenario como descrito anteriormente.
No construtor da classe, inclui os passos que a estória deve seguir para ser satisfeita e como quebrei a estória em três pedaços, precisei incluir três passos sequenciados.
Criando os passos da estória
Como a estória foi quebrada em três partes, se faz por necessário a criação de 3 classes que irão representar os passos da estória e todas essas classes devem herdar a classe Steps do JBehave.
Pela ordem definida na nossa classe de cenário, vamos aos passos:
PromocaoSteps.java
| Java | | copiar código | | ? |
| 01 | package scenario1.steps; |
| 02 | |
| 03 | import java.util.List; |
| 04 | |
| 05 | import junit.framework.Assert; |
| 06 | |
| 07 | import org.jbehave.scenario.annotations.Given; |
| 08 | import org.jbehave.scenario.annotations.Then; |
| 09 | import org.jbehave.scenario.annotations.When; |
| 10 | |
| 11 | import scenario1.entity.Loja; |
| 12 | import scenario1.entity.Produto; |
| 13 | import scenario1.util.PtBRSteps; |
| 14 | import scenario1.vo.Sacola; |
| 15 | |
| 16 | public class PromocaoSteps extends PtBRSteps { |
| 17 | Loja loja = new Loja(); |
| 18 | Sacola sacola = new Sacola(); |
| 19 | List<Produto> listaProdutoPromocao; |
| 20 | int quantidadeProdutoPromocao; |
| 21 | |
| 22 | @Given("Que uma loja possui $quant produtos") |
| 23 | public void populaLoja(Integer quantidade) { |
| 24 | loja.inicializaProdutos(quantidade); |
| 25 | } |
| 26 | |
| 27 | @Given("Que $quant estão em promoção") |
| 28 | public void informaProdutosPromocao(Integer quantidade) { |
| 29 | loja.colocaProdutosPromocao(quantidade); |
| 30 | quantidadeProdutoPromocao = quantidade; |
| 31 | } |
| 32 | |
| 33 | @When("Eu verifico quais estão em promoção") |
| 34 | public void verificaProdutosPromocao() { |
| 35 | listaProdutoPromocao = loja.retornaProdutosPromocao(); |
| 36 | } |
| 37 | |
| 38 | @Then("Preencho minha sacola apenas com produtos em promoção") |
| 39 | public void populaSacola() { |
| 40 | sacola.populaSacola(listaProdutoPromocao); |
| 41 | |
| 42 | Assert.assertEquals(listaProdutoPromocao.size(), quantidadeProdutoPromocao); |
| 43 | } |
| 44 | } |
CompraSteps.java
| Java | | copiar código | | ? |
| 01 | package scenario1.steps; |
| 02 | |
| 03 | import java.util.ArrayList; |
| 04 | import java.util.List; |
| 05 | |
| 06 | import junit.framework.Assert; |
| 07 | |
| 08 | import org.jbehave.scenario.annotations.Given; |
| 09 | import org.jbehave.scenario.annotations.Then; |
| 10 | import org.jbehave.scenario.annotations.When; |
| 11 | |
| 12 | import scenario1.entity.Produto; |
| 13 | import scenario1.util.PtBRSteps; |
| 14 | import scenario1.vo.Sacola; |
| 15 | |
| 16 | public class CompraSteps extends PtBRSteps { |
| 17 | Sacola sacola = new Sacola(); |
| 18 | double somatorioProdutos = 0d; |
| 19 | |
| 20 | @Given("Que minha sacola de compras está preenchida com $quant produtos em promoção") |
| 21 | public void verificaSacola(Integer quantidade) { |
| 22 | List<Produto> listaProduto = new ArrayList<Produto>(); |
| 23 | |
| 24 | for (int i=1; i<=quantidade; i++) { |
| 25 | Produto produto = new Produto(); |
| 26 | produto.preco = Math.round(Math.random()*100); |
| 27 | produto.emPromocao = true; |
| 28 | |
| 29 | listaProduto.add(produto); |
| 30 | } |
| 31 | |
| 32 | sacola.populaSacola(listaProduto); |
| 33 | } |
| 34 | |
| 35 | @When("Eu verifico o somatório de produtos") |
| 36 | public void verificaTotalProdutosSacola() { |
| 37 | for ( Produto produto : sacola.retornaProdutos() ) { |
| 38 | somatorioProdutos = produto.preco + somatorioProdutos; |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | @Then("Devo ter somente um total de R$ $total reais em produtos") |
| 43 | public void validaSacola(double totalProduto) { |
| 44 | if ( somatorioProdutos > totalProduto ) |
| 45 | Assert.fail("Total de produtos é acima de R$ 500"); |
| 46 | } |
| 47 | } |
| 48 |
EmailSteps.java
| Java | | copiar código | | ? |
| 01 | package scenario1.steps; |
| 02 | |
| 03 | import static org.jbehave.Ensure.ensureThat; |
| 04 | |
| 05 | import org.jbehave.scenario.annotations.Given; |
| 06 | import org.jbehave.scenario.annotations.Then; |
| 07 | import org.jbehave.scenario.annotations.When; |
| 08 | |
| 09 | import scenario1.util.PtBRSteps; |
| 10 | import scenario1.vo.Sacola; |
| 11 | |
| 12 | import com.guilhermechapiewski.fluentmail.email.EmailMessage; |
| 13 | import com.guilhermechapiewski.fluentmail.transport.EmailTransportConfiguration; |
| 14 | |
| 15 | public class EmailSteps extends PtBRSteps { |
| 16 | Sacola sacola = new Sacola(); |
| 17 | Boolean statusEnvio = false; |
| 18 | |
| 19 | @Given("Que minha sacola está preenchida de produtos") |
| 20 | public void verificaSacola() { |
| 21 | System.out.println("Lista cheia.."); |
| 22 | } |
| 23 | |
| 24 | @When("Enviar um email de confirmação") |
| 25 | public void enviaEmail() { |
| 26 | EmailTransportConfiguration.configure("localhost", false, false, "", ""); |
| 27 | |
| 28 | new EmailMessage() |
| 29 | .from("eusou@marcuscavalcanti.net") |
| 30 | .to("eusou@marcuscavalcanti.net") |
| 31 | .withSubject("Testando BDD") |
| 32 | .withBody("Testando terceiro cenário e o final da estória.") |
| 33 | .send(); |
| 34 | |
| 35 | statusEnvio = true; |
| 36 | } |
| 37 | |
| 38 | @Then("Devo receber o status do envio \"$retorno\"") |
| 39 | public void recebeStatusEnvio(String retorno) { |
| 40 | ensureThat(statusEnvio.toString().equals(retorno)); |
| 41 | } |
| 42 | } |
Nos exemplos acima, que representam os passos de um cenário, é importante notar que os métodos seguem a ordem definida no arquivo texto, assim como todos são anotados com as palavras chaves correspondentes, no caso Given, When, And, Then. As anotações recebem como parâmetro as frases descritas no arquivo texto, excluindo apenas as palavras chaves no começo da frase, que estão devidamente representadas pelas anotações.
Os parâmetros são identificados com o formato $variavel, e uma vez que usamos esses parâmetros, devemos também colocá-los na assinatura dos métodos. Esses parâmetros são automaticamente capturados pelo JBehave através do uso de expressões regulares.
Executando o exemplo
Para executar o exemplo deve-se executar a classe que extende a classe Scenario do JBehave, nesse caso seria a classe PrecoPromocaoTeste.java, e para acompanhar os resultados, eu utilizo a integração do Eclipse com o JUnit, através de uma view própria da IDE.
No exemplo, usei ainda algumas classes auxiliares criadas especificamente para o exemplo, mas que não irei mostrar aqui os seus códigos para não tornar o tópico extenso demais. De qualquer forma, ao final desse tópico botarei um link para download do projeto no Eclipse que contém todos os códigos.
Observações finais
Um dos problemas que eu notei no JBehave é em relação a parametrização de alguns valores no arquivo texto para o código Java, em alguns casos isso pode se tornar um trabalho sacal e pouco produtivo, mas acredito que com a evolução do framework esse problema será resolvido.
Algumas pessoas utilizam alternativas variadas para resolver esse problema, como por exemplo criar o valor dos parâmetros diretamente como atributos nas classes que representam os passos. Eu particularmente não gosto dessa abordagem, pois acredito que isso faz com que os arquivos que descrevem os cenários percam seu valor e esses arquivos são essencial para utilização e sentido do BDD.
No exemplo citado, usei ainda algumas bibliotecas como FluentMailApi, Mail e Activation da JavaMail e Hamcrest. Essas bibliotecas servem basicamente para o envio de email e uso mais aperfeiçoado de expressões regulares por parte do JBehave.
Para finalizar, é importante citar que as classes do meu negócio (Sacola, Usuario, Loja, etc) foram construídas baseadas no meu requesito, no meu cenário, que foi a primeira etapa a ser elaborada na construção do exemplo.
Download
Para download do projeto completo no Eclipse, basta clicar aqui.
Se você gostou desse tópico, por favor considere deixar um comentário ou se inscreva no feed e tenha no futuro todos os tópicos entregues diretamente no seu agregador.





Excelente artigo, Marcus ! Parabéns !
Obrigado Israel, que bom que ficou claro! :)
[]s