Desenvolvimento orientado por comportamento (BDD)
Neste artigo, veremos uma técnica para desenvolvimento ágil conhecida como Behaviour Driven Development (ou BDD). Mostraremos com exemplos práticos como BDD encoraja a colaboração entre desenvolvedores.
Neste artigo, veremos uma nova técnica para desenvolvimento ágil conhecida como Behaviour Driven Development (ou BDD). Mostraremos com exemplos práticos como BDD encoraja a colaboração entre desenvolvedores, setores de qualidade e pessoas de negócios em um projeto de software.
Guia do artigo:
- O que é TDD?
- BDD, a evolução
- Vantagens em usar BDD
- Por que comportamento?
- BDD na prática
- JBehave
- EasyB
- Spock
Esta metodologia é útil em projetos de software ágeis, que são construídos em várias iterações e estão sofrendo alterações ao longo do seu ciclo de vida. Quanto maior o projeto, mais difícil será a comunicação. Entretanto, BDD propõe uma forma eficaz de resolver estes problemas.
Este artigo faz uma breve introdução ao Desenvolvimento Orientado por Comportamento e demonstra como aplicar esta técnica utilizando os frameworks JBehave, EasyB e Spock. Os exemplos foram criados de uma forma que permita ao leitor comparar estas ferramentas e escolher a mais adequada à realidade de sua equipe.
Quando se estuda Engenharia de Software, aprende-se que todo sistema tem um tempo de vida útil, e que uma série de fatores podem contribuir para aumentar ou diminuir este tempo: arquitetura, modelagem, tecnologia utilizada, aceitação do mercado, etc. No entanto, nenhuma empresa desenvolve um sistema pensando no dia em que ele deixará de atender as necessidades do seu cliente, apesar desta ser uma verdade certa.
Neste ciclo, o sistema passa a maior parte do tempo sofrendo alterações. E estas manutenções, sejam elas corretivas ou não, podem melhorar ou piorar o sistema, dependendo da forma que forem implementadas. Pensando neste e em outros problemas, Kent Beck apresentou ao mundo em 2003, através do seu livro “Test-Driven Development”, uma técnica para criar sistemas baseados em testes que visa garantir a qualidade e a funcionalidade do software durante este ciclo.
Embora TDD seja uma técnica testada e aprovada por grandes desenvolvedores ágeis, muitas equipes de desenvolvimento ainda caem em algumas armadilhas e mal-entendidos do tipo: por onde começar, o que testar e o que não testar. Isto sem falar que quem escreve os testes são os desenvolvedores, mas quando a equipe de qualidade vai testar, ela se preocupa com o comportamento do sistema e não com os testes unitários. Desta forma, não há comunicação eficiente entre as duas equipes no nível de código.
Para tornar a aplicação de TDD mais simples e ajudar as equipes de desenvolvimento a resolverem as questões mencionadas acima, surgiu o Behaviour Driven Development (BDD). Neste artigo, veremos como aplicar esta técnica de forma eficiente, utilizando os frameworks mais conhecidos da comunidade Java.
O que é TDD?
Test Driven Development(TDD) ou, em português, Desenvolvimento Dirigido por Testes é uma técnica dedesenvolvimento de softwareque se baseia em um ciclo curto de repetições. Primeiramente o desenvolvedor escreve um caso de teste automatizado que define uma melhoria desejada ou uma nova funcionalidade. Então, é produzido código que possa ser validado pelo teste. Posteriormente este código será refatorado para colocá-lo sob padrões aceitáveis.
Kent Beck declarou em 2003 que TDD encoraja designs de código simples e inspira confiança. Do ponto de vista de Robert C. Martin, escritor do livro “Agile Software Development”, o objetivo de TDD é a especificação e não a validação, ou seja, é a maneira de pensar através das necessidades do projeto antes de escrever o código funcional.
BDD, a evolução
BDD é técnica de desenvolvimento ágil que visa integrar regras de negócios com linguagem de programação, focando o comportamento do software. Além disso, pode-se dizer também, que BDD é a evolução do TDD. Isto porque, os testes ainda orientam o desenvolvimento, ou seja, primeiro se escreve o teste e depois o código.
O foco em BDD é a linguagem e as interações usadas no processo de desenvolvimento de software. Desenvolvedores que se beneficiam destas técnicas escrevem os testes em sua língua nativa em combinação com a linguagem ubíqua (Ubiquitous Language). Isso permite que eles foquem em por que o código deve ser criado, ao invés de detalhes técnicos, e ainda possibilita uma comunicação eficiente entre as equipes de desenvolvimento e testes.
Linguagem Ubíqua: é uma linguagem estruturada em torno do modelo de domínio e usada por todos os membros da equipe para conectar todas as suas atividades com o software. Numa equipe de desenvolvedores são: os jargões técnicas, terminologias das discussões do dia-a-dia ou uma linguagem incomum para pessoas de outros departamentos.
A linguagem de negócio usada em BDD é extraída das estórias ou especificações fornecidas pelo cliente durante o levantamento dos requisitos. Alguns frameworks utilizam o conteúdo das estórias escritas em um arquivo de texto como cenários para os testes. Quando Dan North apresentou este conceito em 2003, ele sugeriu um padrão para escrita destes arquivos. Veja na Figura 1. Este é apenas um modelo, ou seja, não é obrigatório. Entretanto, Dan North denota que é extremamente importante a equipe seguir um padrão para facilitar a comunicação entre os envolvidos no projeto.
O primeiro framework de BDD, JBehave, foi criado por Dan North, seguido de um framework em Ruby chamado RBehave, o qual foi depois incorporado ao projeto RSpec. Ele também trabalhou com David Chelimsky, Aslak Hellesøy entre outros para desenvolver o framework RSpec e também escrever o livro “The RSpec Book: Behaviour Driven Development with RSpec, Cucumber, and Friends”.
Vantagens em usar BDD
Quando Dan North fala sobre BDD, ele sempre esclarece que o propósito não é anular as práticas de TDD, muito pelo contrário, é adicionar a elas uma série de outras vantagens, dentre as quais se podem listar:
- Comunicação entre equipes: na maioria das empresas de desenvolvimento de software é difícil fazer com que desenvolvedores e testadores trabalhem em conjunto para atingir um objetivo. BDD possibilita esta integração porque os testadores podem escrever os cenários de testes para os desenvolvedores implementarem;
- Compartilhamento de conhecimento: com desenvolvedores e testadores trabalhando juntos, ao longo do tempo, um irá transferir o seu conhecimento para o outro, criando assim uma equipe multifuncional;
- Documentação dinâmica: algumas equipes ágeis afirmam que não documentam o sistema porque a manutenção destes artefatos é custosa. Usando os frameworks de BDD estes artefatos são gerados dinamicamente sem nenhum esforço adicional. Alguns, inclusive, geram relatórios em formato HTML, o que irá facilitar uma consulta posterior;
- Visão do todo: Fergus O’Connell, em sua obra “How to Run Successful High-Tech Project-Based Organizations”(Artech House, 1999), apresenta uma relação dos principais motivos que levamprojetos de software ao fracasso. O primeiro deles é: “os objetivos do projeto não são bem definidos e compartilhados entre todos os envolvidos”. Por este motivo, BDD sugere que os analistas/testadores escrevam os cenários antes mesmo dos testes serem implementados, e desta forma os desenvolvedores terão uma visão geral do objetivo do projeto antes de codificá-lo.
Não restam dúvidas de que desenvolver uma aplicação guiada por testes é uma prática extremamente benéfica. O resultado é um código de boa qualidade, alta coesão e com número reduzido de bugs, com isso proporcionando um prazo de validade longo e manutenções mais baratas no futuro. No entanto, iniciar no mundo dos testes não é nada fácil.
Um dos pontos que complicam a vida de quem quer escrever código guiado por testes geralmente é: por onde começar? Muitas vezes, por conta disso, o resultado é a escrita dos testes após a criação do código de produção, resultando em um código nem sempre coeso e com um acoplamento um pouco duvidoso. Nesse ponto o BDD preenche uma lacuna importante: criar uma conexão entre a definição do negócio e a criação dos testes. Ambos, Behavior-Driven Development e Test-Driven Development, partem do princípio de se criar os testes para guiar a escrita do código de produção, a principal diferença está na definição de comportamento.
Por que comportamento?
Segundo David Chelimsky, um dos grandes problemas que as equipes de desenvolvimento enfrentam hoje é a comunicação. Dificilmente um analista de negócios, ao solicitar uma funcionalidade aos desenvolvedores, diria desta forma: “quando o cliente incluir uma ordem de serviço, esta deve ser armazenada em ANSI-Compliant relacional de banco de dados”. Mas provavelmente ele diria algo assim: “quando o cliente incluir uma ordem de serviço, ela deve ser armazenada no sistema”. Claro que existem empresas que contam com analistas que têm um conhecimento técnico avançado, entretanto, eles não estão preocupados onde e como as informações serão armazenadas, e sim, que o sistema se comporte como o cliente espera.
A orientação a objetos prega uma forma de desenvolver programas que usem elementos do mundo real. Por exemplo, se o sistema que estiver sendo desenvolvido for para um supermercado, certamente ele terá uma classe para representar o produto, outra para representar o cliente, o fornecedor, etc. Desta forma é fácil entender porque BDD se preocupa em testar o que o objeto faz e não o que ele é. Porque isto é, de fato, o que acontece no mundo real.
BDD ajuda a simplificar a comunicação usando cenários descritos pelo cliente ou analista. Cada cenário é dividido em no míninio três blocos, e estes, são definidos pelas palavras chaves:
- Given – dado um contexto;
- When – quando acontecer um evento;
- Then – então se espera que aconteça algo.
A Listagem 1 apresenta um exemplo de uma classe de testes escrita usando esta técnica com um framework de testes chamado Spock.
import spock.lang.Specification;
class JavaMagazineServiceTest extends Specification{
def service
//Método que inicializa os recursos necessários para o teste
def setup(){
service = new JavaMaganizeService()
service.servicoDeEmail = Mock(ServicoDeEmail)
}
//Cenário do teste
def "informar assinantes sobre nova edição da revista"(){
//Dado um contexto (cria uma nova edição da revista)
given:"uma nova edição da revista"
def novaEdicao = new Revista(revistaKey: 100)
//Quando acontecer um evento (publica a edição criada)
when:"for publicada"
service.publicar(novaEdicao)
//Então se espera que aconteça algo (verifica se o método que envia o e-mail foi executado)
then:"deve informar os assinantes"
1* service.servicoDeEmail.avisarAssinantes()
}
}
Este teste consiste em garantir que quando uma nova edição da revista for publicada os assinantes serão informados via e-mail. A classe responsável por esta regra de negócio é a JavaMagazineService, que por sua vez, é instanciada no método setup(). Em seguida, está definido o método que contém o cenário descrito pelo cliente. Quanto à sintaxe do código, será explicado mais adiante neste artigo. O propósito deste exemplo é apenas mostrar que lendo as descrições dos blocos já é possível entender o que o teste faz.
BDD na prática
Existe atualmente cerca de 40 frameworks de BDD open-source, dentre os quais, pelo menos 10 são para Java ou Groovy. No entanto, neste artigo, serão mostrados apenas alguns utilizados pela comunidade Java: JBehave, EasyB e Spock.
Para facilitar a compreensão do leitor e possibilitar uma comparação entre os frameworks, será utilizado o mesmo caso de teste em quase todos os exemplos.
Suponha que o cliente solicite um novo módulo no sistema bancário que possibilite aos seus correntistas sacar dinheiro no caixa eletrônico. No entanto, existem dois tipos de correntistas: o especial e o comum. O especial pode realizar saques mesmo que o saldo da sua conta esteja negativo, já o comum só pode realizar o saque se houver saldo disponível. Neste caso, percebem-se claramente dois cenários distintos: um para o cliente especial e outro para o cliente comum. A Figura 2 exibe a Estória fornecida pelo cliente.
Para saber mais sobre Estórias do Usuário, leia o artigo “Melhorando a comunicação com Estórias do Usuário”, publicado na Java Magazine 86.
O conteúdo da classe que representa o cliente está na Listagem 2. Em um projeto real, talvez o método que libera o saque estaria na camada de negócio (um service), mas como o objetivo é apenas explicar o comportamento da funcionalidade, este código foi colocado na própria classe Cliente, que por sua vez, tem apenas dois atributos: saldoAtual e uma flag indicando se é cliente especial ou não. O método sacar() é responsável por liberar ou não o saque dependendo do tipo de cliente e do valor do saldo atual. As boas práticas de TDD ensinam que um método como este deveria ser implementado e evoluído ao longo do teste. No entanto, o objetivo deste artigo não é ensinar TDD e sim mostrar como usar as ferramentas de BDD existentes no mercado. Por este motivo ele já está totalmente desenvolvido.
package main.model;
public class Cliente {
private boolean clienteEspecial;
private Double saldoAtual;
public boolean isClienteEspecial() {
return clienteEspecial;
}
public Double getSaldoAtual() {
return saldoAtual;
}
public void clienteEspecial() {
this.clienteEspecial = true;
}
public void clienteComum() {
this.clienteEspecial = false;
}
public void setSaldoAtual(Double saldoAtual) {
this.saldoAtual = saldoAtual;
}
//Método responsável por liberar ou não o saque
public boolean sacar(Double valorDoSaque) throws Exception {
//Se o saldo for ficar negativo
if(this.saldoAtual < valorDoSaque){
//E o cliente for especial
if(isClienteEspecial()){
//Libera o saque e atualiza o saldo
this.atualizarSaldo(valorDoSaque);
return true;
//Se o cliente for comum
} else {
//Não libera o saque e retorna uma mensagem na Exception
throw new Exception("Saldo Insuficiente");
}
//Se o saldo for ficar positivo
} else {
//Libera o saque e atualiza o saldo
this.atualizarSaldo(valorDoSaque);
return true;
}
}
private void atualizarSaldo(Double valorDoSaque) {
this.saldoAtual -= valorDoSaque;
}
}
JBehave
JBehave é um framework de testes para BDD. A filosofia que levou a sua criação é a mesma que originou o desenvolvimento orientado por comportamento, ou seja, facilitar a comunicação dos requisitos de negócio entre as partes interessadas e a equipe de desenvolvimento, e permitir que estes comportamentos sejam verificados de forma automática através de integração contínua.
As equipes que utilizam metodologias ágeis como Scrum, Kanban e Estórias de Usuário, costumam optar por este framework devido ao fato dos testes serem escritos em forma de cenários e serem disticutidos na reunião de planejamento de cada iteração. Isto pode ser feito pela equipe de desenvolvimento, por um analista de negócios, ou também pode ser escrito pelos próprios interessados.
Este foi o primeiro framework de BDD para testar aplicações Java. Criado por Dan North, a ideia foi tão boa que vários outros frameworks foram desenvolvidos utilizando o mesmo conceito.
Iniciando com JBehave
Antes que começar o exemplo com JBehave, baixe-o no site oficial do framework (veja o endereço na seção Links) e adicione-o no seu projeto. Caso utilize o gerenciador de dependências Maven, basta adicionar o conteúdo da Listagem 3 no POM.xml.
No JBehave, a fonte de dados para os testes é extraída das Estórias de Usuário. Para aplicação do exemplo, será usada a estória ilustrada na Figura 2. Será criado também um arquivo texto contendo a estória e os dois cenários encontrados. Este arquivo pode ser criado em qualquer editor de texto e não deve ter extensão. Nomeie-o como caixa_eletronico_test e salve-o dentro do projeto. Caso prefira outro nome e ele seja composto, é necessário escrevê-lo em letra minúscula e separado por underline (“_”), caso contrário o framework não conseguirá localizá-lo. O conteúdo do arquivo está na Listagem 4.
O primeiro parágrafo do arquivo contém a estória descrita no cartão (observe a Figura 2). A palavra que inicia o parágrafo (Story) pode ser escrita em qualquer idioma, mas deve ser algo que indica a narrativa de uma estória. Neste arquivo quase tudo pode ser escrito no idioma local, exceto as palavras: “Scenario”, “Given”, “When” e “Then”, que são palavras-chaves usadas para conectar o texto aos métodos das classes. No entanto, caso prefira escrever tudo em português, o leitor pode baixar uma contribuição feita pelo amigo Emerson Macedo (http://codificando.com/2009/04/jbehave-brasil-bdd-em-java-no-nosso-idioma/), que estendeu as classes do JBehave possibilitando a escrita em nosso idioma.
Cada arquivo texto deve possuir uma classe principal que o represente. O nome desta classe precisa ter relacionamento com o nome do arquivo onde estão descritos os cenários. Neste caso, esta classe deve se chamar CaixaEletronicoTest.java, trocando os “_” por CamelCase. A classe será armazenada no mesmo diretório do arquivo. Veja o conteúdo dela na Listagem 5. Repare que houve uma extensão da classe Scenario. Isto é necessário para termos disponível na classe criada, os recursos do JBehave.
CamelCase: CamelCaseé a denominação eminglêspara a prática de escrever palavras compostas oufrases, onde cada palavra é iniciada com maiúsculase unidas sem espaços. É um padrão largamente utilizado em diversaslinguagens de programação, comoJava,Ruby,PHPe Python, principalmente nas definições deClasseseObjetos. Pela sua associação com atecnologia, o marketing se apropriou dessa maneira de escrever, injetando certo ar de "tecnologia" nos produtos assim nomeados:iPod,GameCube,OpenOffice.org,StarCraft, dentre outros.
Executando o teste com JUnit4 neste ponto, o resultado será algo parecido com o ilustrado na Figura 3. Observe que as linhas dos cenários estão todas pendentes (“PENDING”). Isto acontece porque nenhuma linha de código está associada aos cenários ainda.
O próximo passo será adicionar um construtor à classe CaixaEletronicoTest, conforme a Listagem 6. Neste construtor, serão incluídos ao teste dois objetos do tipo Steps. Os Steps são classes que executam os cenários descritos no arquivo texto (veja a Figura 4). Neste exemplo, serão usados dois cenários, portanto dois steps. O nome das classes steps não faz diferença. Entretanto precisam ser instanciadas no início do teste pelo objeto principal (CaixaEletronicoTest). Veja o conteúdo dos Steps nas Listagens 7 e 8.
package test.scenario;
import org.jbehave.scenario.Scenario;
public class CaixaEletronicoTest extends Scenario {
//Construtor
public CaixaEletronicoTest() {
//Cria Steps que irão executar os cenários do arquivo texto.
addSteps(new SaqueDeClienteEspecialComSaldoNegativo());
addSteps(new SaqueDeClienteComumComSaldoNegativo());
}
}
1. package test.step;
2. import org.jbehave.scenario.annotations.Given;
3. import org.jbehave.scenario.annotations.Then;
4. import org.jbehave.scenario.annotations.When;
5. import org.jbehave.scenario.steps.Steps;
6. import main.model.Cliente;
7. import static org.junit.Assert.*;
8. public class SaqueDeClienteEspecialComSaldoNegativo extends Steps {
//Objeto que representa o cliente
9. private Cliente cliente;
//Variável usada para validar se o saque foi ou não liberado
10. private boolean saqueEfetuado = false;
//Instancia o cliente e define o saldo inicial
11. @Given("um cliente especial com saldo atual de $saldoAtual reais")
12. public void popularCliente(Double saldoAtual) {
13. cliente = new Cliente();
14. cliente.setSaldoAtual(saldoAtual);
15. cliente.clienteEspecial();
16. }
//Efetua o saque e armazena o retorno na variável que será testada a seguir
17. @When("for solicitado um saque no valor de $valorDoSaque reais")
18. public void sacar(Double valorDoSaque) throws Exception{
19. saqueEfetuado = cliente.sacar(valorDoSaque);
20. }
//Verifica se o saque foi efetuado e se o saldo foi atualizado
21. @Then("deve efetuar o saque e atualizar o saldo da conta para $novoSaldo 22. reais")
23. public void verificaOSaldo(Double novoSaldo){
24. assertTrue(saqueEfetuado);
25. assertEquals(novoSaldo, cliente.getSaldoAtual(), 0d);
26. }
27.}
1. package test.step;
2. import static org.junit.Assert.*;
3. import main.model.Cliente;
4. import org.jbehave.scenario.annotations.Given;
5. import org.jbehave.scenario.annotations.Then;
6. import org.jbehave.scenario.annotations.When;
7. import org.jbehave.scenario.steps.Steps;
8. public class SaqueDeClienteComumComSaldoNegativo extends Steps {
//Objeto que representa o cliente
9. private Cliente cliente;
//Variável usada para validar se o saque foi ou não liberado
10. private boolean saqueEfetuado = false;
//Variável usada para armazenar a mensagem que será validada a seguir
11. private Exception exception;
//Instancia o cliente e define o saldo inicial
12. @Given("um cliente comum com saldo atual de $saldoAtual reais")
13. public void popularCliente(Double saldoAtual) {
14. cliente = new Cliente();
15. cliente.setSaldoAtual(saldoAtual);
16. cliente.clienteComum();
17. }
//Efetua o saque e armazena a mensagem de retorno na variável que será testada a seguir
18. @When("solicitar um saque de $valorDoSaque reais")
19. public void sacar(Double valorDoSaque){
20. try {
21. cliente.sacar(valorDoSaque);
22. } catch (Exception e) {
23. this.exception = e;
24. }
25. }
//Validar se o saque foi negado e se mensagem de retorno foi a mesma que a especificada no cenário
26. @Then("não deve efetuar o saque e retornar a mensagem $msg")
27. public void verificaOSaldo(String msg){
28. assertFalse(saqueEfetuado);
29. assertEquals(msg, exception.getMessage());
30. }
31.}
Entendendo os Steps do JBehave
Para criar um Step no JBehave é necessário estender a classe Steps (org.jbehave.scenario.steps.Steps). Cada método desta classe representa uma parte do cenário e estão conectados através das anotações: @Given, @When e @Then (veja o exemplo na Figura 5).
Na classe SaqueDeClienteEspecialComSaldoNegativo (exibida na Listagem 7), o método popularCliente() – linha 12 – instancia o objeto Cliente, define o saldo inicial e o tipo do cliente. O método sacar() – linha 18 – efetua o saque e armazena o resultado na variável saqueEfetuado para ser testada no método verificarOSaldo() – linha 23. Além disto, este método ainda verifica se o saldo do cliente foi alterado. Os valores de: saldoAtual – linha 12 – e valorDoSaque – linha 18 – são extraídos do arquivo texto e passados para os métodos através de expressões regulares (regex). A forma mais fácil de fazer isto é colocando as frases nas anotações idênticas às do arquivo texto, substituindo apenas os valores por variáveis. Veja um exemplo na linha 11.
O comportamento da classe SaqueDeClienteComumComSaldoNegativo (Listagem 8) é muito parecido com a anterior. A única diferença é que o cliente não pode efetuar o saque. Desta forma, o método sacar() – linha 19 – retorna uma exceção contendo uma mensagem para ser testada no método verificarOSaldo() – linha 27.
Para finalizar, execute o teste novamente usando o JUnit4. Se for exibida uma barra verde significa que o teste passou, caso contrário verifique o código e tente novamente. O resultado deve ser algo como o ilustrado na Figura 6.
Vantagens na utilização do JBehave
O fato de JBehave ter sido o primeiro framework para criação de testes orientado a comportamento, o fez ser o mais popular dentre os demais do mesmo gênero. Entretanto, isto também se deve às seguintes vantagens que ele apresenta:
- Totalmente desenvolvido em Java, o que facilita a integração com outros projetos da mesma plataforma;
- Os usuários podem especificar e executar as Estórias de usuário baseadas em arquivo texto;
- Anotações (@annotations) que conectam os textos da estória do usuário com os parâmetros dos métodos;
- Além do console, os resultados dos testes podem ser vistos ainda nos formatos: HTML, TXT e XML. Mas isto requer uma configuração específica que pode ser encontrada na documentação do framework;
- Controle de todas as etapas (métodos) dos cenários pendentes para que não fiquem sem ser implementadas;
- Localização das Estórias através de palavras chaves, podendo assim, serem escritas em qualquer idioma (exceto Given, When e Then);
- Integração com as principais IDEs, entre elas: Eclipse e NetBeans;
- Execução com ANT e Maven.
EasyB
O EasyB é um framework para BDD baseado em Groovy. Como o próprio nome sugere, EasyB é uma das formas mais fáceis de escrever testes orientado a comportamento. Tem como grande vantagem a sintaxe Groovy, o que o torna ágil e fácil de aprender.
As principais diferenças entre EasyB e JBehave estão na sintaxe do código e na forma que a estória é integrada ao teste. Enquanto o JBehave faz a conexão dos textos aos métodos automaticamente através de anotações, o EasyB não faz nenhum tipo de associação dinâmica, ou seja, ou textos são meramente usados para efeito de documentação. Além disso, os cenários não são escritos em um arquivo texto e sim no próprio corpo da classe.
Escrevendo testes com EasyB
Antes de começar o exemplo, baixe o framework (veja o endereço na seção Links) e adicione-o ao seu projeto. Caso utilize o Maven, adicione o conteúdo da Listagem 9 nas dependências do arquivo POM.xml.
<dependency>
<groupId>org.easyb</groupId>
<artifactId>maven-easyb-plugin</artifactId>
<version>0.9.7-1</version>
</dependency>
Para aplicação dos exemplos com EasyB, será usado a mesma estória e cenários da Figura 2. O conteúdo da classe que representa o cliente está na Listagem 2. Como se pode observar, os testes podem ser escritos em formato de estória, conforme a Listagem 10. Neste caso, para serem reconhecidos pelo framework, devem estar com a extensão .story.
No EasyB, nem todos os elementos de uma estória são obrigatórios. A descrição – linha 1 – e a narrativa – linha 2 – servem apenas para documentação e não fazem efeito no código. Contudo, se utilizados, devem empregar as palavras chaves: description (descrição), narrative (narrativa), as_a (como um), i_want (eu gostaria) e so_that (para que). Note que, diferente do JBehave, os cenários foram colocados no mesmo arquivo, o que torna o código mais legível.
Para demonstrar este teste, criamos a classe CaixaEletronico.story com três métodos. O primeiro deles, before_each – linha 8, Listagem 10 –, é responsável por instanciar o objeto que representa o cliente e criar a variável que irá armazenar o resultado do saque. Este método será executado uma vez antes de cada cenário. O segundo método – linha 14 – é composto por blocos que funcionam com se fossem funções. Os blocos do EasyB são equivalentes às anotações do JBehave, inclusive as palavras chaves são as mesmas: given, when e then. Por mais incrível que pareça, as poucas linhas deste método fazem a mesma coisa que a classe SaqueDeClienteEspecialComSaldoNegativo do JBehave (exibida na Listagem 7), com a diferença que o EasyB não usa o Assert do JUnit para fazer as validações, pois tem uma função validadora específica chamada shouldBe – linha 23. O terceiro método é muito parecido com o segundo e tem o mesmo comportamento da classe SaqueDeClienteComumComSaldoNegativo (Listagem 8).
Para executar o teste acima, pode-se usar o Maven ou Ant. Entretanto, caso prefira, recentemente foi criado um plugin para o Eclipse (para mais informações, acesse o endereço em Links) que exibe o resultado no console (veja na Figura 7).
import main.Cliente;
//Descrição do teste (opcional)
description "Cliente faz saque "
//Narrativa da estória (opcional)
narrative 'Saque de Cliente', {
as_a 'cliente'
i_want 'de sacar dinheiro em caixa eletrônico'
so_that 'eu não tenha que esperar numa fila de banco'
}
//Método que será executado antes de cada cenário
//Instancia o cliente e cria a variável que irá armazenar o retorno do saque
before_each "",{
given "um cliente",{
cliente = new Cliente()
saqueEfetuado = false
}
}
//Primeiro Cenário
scenario "cliente especial com saldo negativo",{
given "um cliente especial com saldo atual de -200 reais",{
cliente.saldoAtual = -200;
cliente.clienteEspecial();
}
when "for solicitado um saque no valor de 100 reais", {
saqueEfetuado = cliente.sacar(100);
}
then "deve efetuar o saque e atualizar o saldo da conta para -300 reais", {
saqueEfetuado.shouldBe true
(cliente.saldoAtual == -300).shouldBe true
}
}
//Segundo Cenário
scenario "cliente comum com saldo negativo",{
given "um cliente comum com saldo atual de -300 reais",{
cliente.saldoAtual = -300;
cliente.clienteComum();
}
when "for solicitado um saque no valor de 200 reais", {
try{
cliente.sacar(200)
} catch (Exception e) {
msg = e.message
}
}
then "não deve efetuar o saque", {
saqueEfetuado.shouldBe false
}
and "deve retornar a mensagem Saldo Insuficiente",{
msg.shouldBe 'Saldo Insuficiente'
}
}
O EasyB ainda proporciona uma forma mais agradável de testadores e analistas visualizarem o resultado dos testes através de relatórios HTML, que ocultam o código, exibindo apenas detalhes de negócio. Veja um exemplo na Figura 8. Este tipo de relatório é gerado automaticamente quando o teste é executado pelo Maven (Figura 9). Para isso, basta acrescentar o conteúdo da Listagem 11 no POM.xml e colocar a classe de teste no pacote src/test/easyb.
<build>
<plugins>
<plugin>
<groupId>org.easyb</groupId>
<artifactId>maven-easyb-plugin</artifactId>
<version>0.9.7-1</version>
<executions>
<execution>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
<configuration>
<storyType>html</storyType>
<storyReport>
${project.build.directory}/easyb/stories.html
</storyReport>
</configuration>
</plugin>
</plugins>
</build>
Outra forma de escrever testes no EasyB é através de especificações. Neste caso, a extensão da classe deve ser .specification.
Para entender melhor este formato, observe o exemplo ilustrado na Listagem 12. Note que ele foge um pouco dos exemplos anteriores. Isto porque, não faz muito sentido escrever a estória da Figura 2 em forma de especificações. Neste caso, vamos apenas incluir e retirar elementos de um objeto do tipo Stack (pilha) para demonstrar a sintaxe deste formato. Observe que cada método é iniciado com a palavra-chave “it”. O nome dos métodos pode ser escrito em forma de frase e em qualquer idioma. O primeiro método, before – linha 2 –, será executado uma vez antes de cada método. Assim como nos exemplos anteriores, este método será responsável por inicializar todos os recursos em comum do teste. No segundo método – linha 5 –, inserimos um elemento na pilha e depois verificamos o tamanho dela. No terceiro método – linha 9 –, tentamos retirar um elemento de uma pilha vazia para exemplificar o tratamento de exceções usando a função ensureThrows() – linha 10. Por fim, no quarto e último método – linha 14 –, empilhamos uma série de elementos e em seguida verificamos a existência deles na pilha.
Para finalizar, basta executar o teste usando o plugin do EasyB. O Eclipse exibirá, no console, uma linha para cada método com o resultado do teste.
description "Testando uma pilha"
before "inicializa a fila para cada especificação", {
pilha = new Stack<Integer>()
}
it "deve possuir apenas 1 elemento", {
pilha.push(2)
pilha.size().shouldBe(1)
}
it "deve retornar uma exception quando for retirado um elemento da pilha vazia", {
ensureThrows(Exception.class) {
pilha.pop()
}
}
it "deve empilhar e desempilhar os elementos na mesma ordem", {
[1..5].each { val ->
pilha.push(val)
}
[1..5].each { val ->
pilha.pop().shouldBe(val)
}
}
Vantagens na utilização do EasyB
O EasyB foi desenvolvido para facilitar o dia-a-dia dos programadores Java com a criação de testes orientados a comportamento. As principais vantagens deste framework são:
- Utiliza a linguagem Groovy, que possibilita a escrita de código com muito mais produtividade;
- Possui dois formatos de teste. A possibilidade de escrever o código em forma de estória ou especificação torna o EasyB uma boa alternativa para equipes que trabalham ou não com Estórias de Usuário;
- Possui plugin para o Eclipse, o que facilita a criação de testes nesta IDE;
- Os testes podem ser executados com Maven ou Ant;
- Fácil de aprender;
- Gera relatórios em HTML usando o Maven.
Spock
Spock é um framework de testes de especificações para aplicações Java e Groovy. O que mais chama atenção neste framework é a facilidade de aprendizagem e a alta expressividade do código, tornando-o assim, o passo mais curto para um desenvolvedor Java migrar para BDD usando uma linguagem dinâmica. Isto significa que para criar testes com Spock o desenvolvedor precisa ter noções de Groovy.
Por ser executado com JUnit4, o Spock é compatível com quase todas as IDEs existentes, ferramentas de build e servidores de integração contínua. Ele foi inspirado nos frameworks JUnit, JMock e RSpec, e nas linguagens dinâmicas Groovy e Scala. É uma mistura de tecnologias que deu certo.
O ciclo de vida de um teste Spock pode ser definido em quatro métodos:
- def setup(): executado antes de cada método. É compatível com a anotação @Before do JUnit. Neste método criam-se todos os recursos (objetos, variáveis, etc.) necessários para o teste;
- def cleanup(): executado depois de cada método. É compatível com a anotação @After do JUnit. Neste método eliminam-se todas as fixtures utilizadas para o teste;
- def setupSpec(): executado uma vez antes do primeiro método. É compatível com a anotação @BeforeClass do JUnit;
- def cleanupSpec(): executado uma vez depois do último método. É compatível com a anotação @AfterClass do JUnit.
No Spock, cada método é um cenário composto por até seis blocos: Setup, When, Then, Expect, Cleanup e Where. Nestes blocos, as descrições devem ser colocadas entre aspas duplas, podendo ser escritas em qualquer idioma. Neste aspecto, o Spock é muito parecido com o EasyB.
Praticando BDD com Spock
Antes de começar o próximo exemplo, baixe o framework Spock no endereço informado na seção Links. O pacote disponível para download contém um projeto de exemplo completo com Spock, Maven, Groovy e Gradle. Ou, caso prefira, adicione o conteúdo da Listagem 13 no POM.xml. Nesta listagem constam todos os plugins do Spock, inclusive o pacote completo do Groovy. Caso deseje saber mais sobre as dependências deste framework e personalizar o seu projeto consulte o site do Maven.
Gradle: É um sistema de build baseado em Groovy que trás a expressividade desta linguagem para o mecanismo de build, o que torna os scripts muito mais legíveis e fáceis de escrever. Ele é um grande “concorrente” do Ant e tem conquistado muitas equipes de desenvolvimento porque dispensa extensas configurações em XML. Tudo pode ser feito usando a linguagem Groovy, gerando um código legível e fácil de entender. Para saber mais, veja o endereço do site em Links.
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.3</version>
<configuration>
<providerSelection>1.7</providerSelection>
</configuration>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.gmaven.runtime</groupId>
<artifactId>gmaven-runtime-1.7</artifactId>
<version>1.3</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.spockframework</groupId>
<artifactId>spock-maven</artifactId>
<version>0.5-groovy-1.7</version>
<executions>
<execution>
<goals>
<goal>find-specs</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>0.5-groovy-1.7</version>
<scope>test</scope>
</dependency>
</dependencies>
O exemplo a seguir será baseado na mesma estória usada nos exemplos anteriores (Figura 2), bem como na classe que representa o cliente (Listagem 2).
Para usar o Spock, recomenda-se que o projeto seja do tipo Groovy (versão 1.7 ou superior). Isto porque algumas IDEs têm dificuldade de compilar o código Groovy em um projeto Java. Para fazer a conversão, clique com o botão direito do mouse sobre o projeto e acesse a opção Configure. Em seguida, selecione Convert to Groovy Project (veja a Figura 10). O código não sofrerá alterações e continuará funcionando da mesma forma, já que o Groovy também interpreta código Java.
Neste exemplo, criaremos a classe CaixaEletronicoTest.groovy. O conteúdo dela pode ser visto na Listagem 14. À primeira vista, nota-se uma grande semelhança com a sintaxe do EasyB, mas repare que no Spock os métodos e variáveis são definidos com a palavra-chave “def” (linhas 5 e 11), que aliás é padrão do Groovy. Outra diferença está na forma de fazer as verificações. No EasyB se usa o método shouldBe(), já no Spock as verificações são booleanas, sendo true ou false (observe as linhas 18, 20, 29 e 32).
O primeiro método, setup() – encontrado na linha 7 –, é responsável por instanciar os recursos em comum do teste e é executado uma vez antes de cada cenário. Os outros dois métodos – encontrados nas linhas 11 e 22 – têm o comportamento idêntico aos do EasyB, respectivamente – linhas 13 e 26 da Listagem 10. As diferenças estão na sintaxe e na forma de capturar a exceção. Enquanto o EasyB usa a função ensureThrows(), o Spock usa a função thrown() – linha 31.
Para executar este teste, pode-se usar o JUnit4, Maven ou Ant. No entanto, os dois últimos requerem uma configuração específica. Para saber mais, consulte a documentação do framework. Neste exemplo, usaremos o JUnit4. Observe o resultado do teste na Figura 11.
package test
import main.Cliente
import spock.lang.Specification
class CaixaEletronicoTest extends Specification {
//Objeto que irá representar o cliente
def Cliente cliente
//Variável que será usada para armazenar o retorno do saque
def saqueEfetuado
//Método que inicializa os recursos em comum usados nos dois cenários
def setup(){
cliente = new Cliente()
saqueEfetuado = false
}
//Primeiro Cenário
def "saque de cliente especial com saldo negativo"(){
given:"um cliente especial com saldo atual de -200 reais"
cliente.clienteEspecial()
cliente.saldoAtual = -200
when:"for solicitado um saque no valor de 100 reais"
saqueEfetuado = cliente.sacar(100)
then:"deve efetuar o saque"
saqueEfetuado
and:"atualizar o saldo da conta para -300 reais"
cliente.saldoAtual == -300
}
//Segundo Cenário
def "saque de cliente comum com saldo negativo"(){
given:"um cliente comum com saldo atual de -300 reais"
cliente.clienteComum()
cliente.saldoAtual = -300
when:"for solicitado um saque no valor de 200 reais"
cliente.sacar(200)
then:"não deve efetuar o saque"
!saqueEfetuado
and:"deve retornar a mensagem Saldo Insuficiente"
def e = thrown(Exception)
e.message == "Saldo Insuficiente"
}
}
Vantagens na utilização do Spock
Embora o Spock ainda não seja muito difundido na comunidade Java brasileira, ele tem se mostrado uma boa alternativa para equipes que querem começar com BDD utilizando uma linguagem dinâmica. Suas principais vantagens são:
- Utiliza a linguagem Groovy, que é sinônimo de produtividade para a equipe;
- Possui um formato para escrita de testes bastante intuitivo e fácil de ler.
- Usa JUnit4, portanto pode ser usado nas principais IDEs existentes;
- Pode ser executado com Maven ou Ant;
- Assim como no EasyB, os cenários podem ser escritos na mesma classe;
- Pode ser usado para fazer testes de aceitação e integração.
Conclusão
Como se pode perceber, utilizando BDD não só melhoramos a comunicação entre desenvolvedores e testadores que passam a trabalhar em conjunto, mas também tornamos o desenvolvimento muito mais objetivo. Além do mais, se a empresa adotar o costume de escrever todos os cenários antes de iniciar a codificação dos testes, fica fácil entender o que o software deve fazer antes de implementá-lo, já que todos os envolvidos possuirão uma visão geral do projeto.
As equipes ágeis que utilizam Estórias de Usuário serão muito mais produtivas se usarem o conteúdo dos testes descritos no verso do cartão para criarem os testes. Isto porque, na prática, é o que o testador irá executar quando o software for finalizado. No entanto, mesmo as equipes que não utilizam estas técnicas podem usufruir dos benefícios de BDD, posto que, geralmente os testadores e analistas de negócio sempre sabem o que esperam do software antes dele ser desenvolvido.
Outro grande benefício que as equipes têm usando BDD é a documentação. Já que alguns frameworks disponibilizam relatórios dinamicamente sem esforço adicional. E mesmo os que não geram relatórios, ainda têm um formato de escrita muito parecida com um documento.
Embora muitas equipes de desenvolvimento utilizem apenas teste unitário para “garantir” a qualidade do sistema, grande parte dos frameworks de BDD vão muito mais além. O EasyB e Spock, por exemplo, também são usados para fazer testes de integração e aceitação, cobrindo assim, quase todas as camadas de um sistema.
Ao usar BDD as equipes não devem descartar as práticas de TDD, pois a primeira é uma evolução da segunda. Se este erro for cometido, BDD não trará grandes proveitos para as equipes, pelo contrário, poderá diminuir drasticamente a cobertura de testes do sistema. Contudo, se usada de forma correta, as práticas de BDD trarão um novo olhar sobre testes para as equipes.
Referências:
Confira também
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo