Behavior Driven Development com JUnit
Postado em 28. fev, 2009 por Lucas Cavalcanti em Agile
Behavior Driven Development (BDD) é uma maneira de desenvolver software que, além de outras coisas, encoraja a escrita de testes mais parecidos com uma especificação, como sugerido pelo Dan North. A ideia é que seus testes descrevam o que um determinado módulo deveria fazer ou como uma determinada funcionalidade deveria funcionar. Algo como:
Dado que eu estou na página principal, quando eu tento me logar, então meu nome aparece no canto da tela.
Eu, Cauê Guerra e Cecilia Fernandes estávamos começando um projeto aqui na Caelum e resolvemos usar BDD no desenvolvimento. Começamos, então, a procurar ferramentas que ajudam o desenvolvimento em BDD. Em Ruby existem o Cucumber e o RSpec, em Groovy tem o Easyb e, em Java, o JBehave, apenas para citar os mais famosos. Começamos a testar essas opções, mas logo no começo surgiram alguns problemas:
- Cucumber + RSpec e Easyb: ambos eram muito parecidos em suas qualidades e defeitos: utilizam arquivos de texto para a descrição das histórias e são bem fáceis de escrever. No entanto, nenhum dos dois é feito em Java. Poderíamos usar, sem problemas, o JRuby e o próprio Groovy para acessar nosso código Java, mas por se tratar de um projeto open-source não queríamos obrigar futuros contribuidores a conhecer outras linguagens para começar a trabalhar em nosso projeto.Um outro problema foi que, usando o Eclipse como IDE, perderíamos o code completion e a habilidade de rodar os testes pela IDE – seria preciso criar alguma task ant ou um Rakefile para isso, rodando tudo pela linha de comando e tendo que interpretar os resultados obtidos fora da IDE.
- JBehave: o JBehave adiciona um pouco de complexidade para escrever os testes (anotações, arquivos de texto não compilados, algumas restrições sobre como as classes devem ser escritas), e achamos que o ganho que teríamos ao usar o JBehave não valeria a pena por causa dessa complexidade. Além disso, teríamos problemas para reaproveitar o código dos steps, dificultando a manutenção dos testes. Existem outros problemas, mas vamos deixar isso para outro post.
Daí nos questionamos: será que precisamos mesmo de alguma ferramenta especial para usar BDD? E a resposta que surgiu foi: Não, podemos fazer tudo usando apenas o JUnit! O bom e velho JUnit! Mas não bastava só usá-lo. Precisávamos de um jeito de organizar como uma especificação. Então pensamos e chegamos no seguinte padrão de testes:
@Test
public void nomeDoCenarioDeTestes() {
given.algumContexto();
when.euExecutoAlgumaAcaoNoSistema();
then.algumaCondiçãoTemQueSerVerdadeira();
}
Por exemplo, usando o cenário que eu tinha escrito acima:
@Test
public void login() {
given.iAmOnTheMainPage();
when.iLoginAs("Lucas");
then.myNameAppearsOnTheScreen("Lucas");
}
Um código simples, que qualquer pessoa que sabe inglês entenderia, e que nos diz o que esperamos que aconteça quando alguém se loga no sistema. Mas o que exatamente são esses “given“, “when” e “then“? São objetos simples, que não precisam implementar nenhuma interface nem estender classe alguma – vão apenas executar o que lhes foi pedido pelo teste.
Mesmo assim resolvemos usar uma convenção para dar o nome a esses objetos, de acordo com suas responsabilidades:
- GivenSteps given => objeto que vai preparar o contexto inicial do teste em questão. Ex: Entrar em uma página, inserir determinados objetos no banco, logar-se com um dado usuário, etc.
- WhenSteps when => objeto que vai executar as ações do teste em si, utilizando o contexto definido pelo objeto given. É a parte mais importante do teste. Ex: Preencher um formulário, clicar no botao Enviar, selecionar um item em alguma comboBox, etc.
- ThenSteps then => objeto que verifica se o resultado das ações executadas é o esperado. Ex: O usuário está logado? Apareceu a mensagem “Inserido com sucesso“? Deu erro de validação?
Para que os testes ficassem o mais limpo possível e o mais parecidos com um arquivo de texto descrevendo os cenários de uma história, separamos toda a configuração de testes e a criação dos nossos objetos given, when e then em uma classe abstrata que vai ser superclasse desses testes – que representam uma história. Também podemos colocar a descrição da história como comentário no começo do arquivo. Vejam um exemplo completo de um Cenário de teste, com todas as classes:
/**
* In order to use the system
* As a user
* I want to login
*/
public class LoginStory extends Story {
@Test
public void login() {
given.iAmOnTheMainPage();
when.iLoginAs("Lucas");
then.myNameAppearsOnTheScreen("Lucas");
}
}
public abstract class Story {
protected GivenSteps given;
protected WhenSteps when;
protected ThenSteps then;
@Before
public void setUp() {
Browser browser = createSeleniumDSL();
given = new GivenSteps(browser);
when = new WhenSteps(browser);
then = new ThenSteps(browser);
}
}
public class GivenSteps {
private final Browser browser;
public GivenSteps(Browser browser) {
this.browser = browser;
}
public void iAmOnTheMainPage() {
browser.open("index.jsp");
}
}
public class WhenSteps {
private final Browser browser;
public WhenSteps(Browser browser) {
this.browser = browser;
}
public void iLoginAs(String login) {
browser.form("login")
.field("username").type(login)
.field("password").type(login)
.submit();
}
}
public class ThenSteps {
private final Browser browser;
public ThenSteps(Browser browser) {
this.browser = browser;
}
public void myNameAppearsOnTheScreen(String login) {
String div = browser.currentPage()
.div("user").innerHTML();
Assert.assertThat(div, containsString(login));
Assert.assertThat(div, containsString("Logout"));
}
}
O arquivo de teste mesmo fica bem limpo, apenas com o roteiro que o teste vai seguir, e o trabalho “sujo” da implementação fica nos given, when e then, que podem ser reutilizados por qualquer teste. Esses objetos não precisam nem ser diferentes, não há necessidade de ter apenas um de cada na aplicação: você pode organizá-los da maneira que quiser. O importante é que no final o seu arquivo de teste fique limpo e escrito como se fosse uma especificaçao de uma funcionalidade ou a descrição de um cenário. Usando esse padrão fica fácil criar geradores de relatório, que analisam o arquivo da história e geram uma página HTML ou um arquivo de texto com as histórias que passaram e as que falharam.
Para conferir o resultado, você pode baixar o projeto que está usando essas idéias em: http://github.com/caueguerra/calopsita.
18 Respostas para “Behavior Driven Development com JUnit”
Trackbacks/Pingbacks
-
-
fevereiro 27, 2010
[...] Também podem encontrar informaćões sobre Behaviour Driven Development neste post. [...]
-
-
setembro 26, 2011
[...] http://blog.caelum.com.br/behavior-driven-development-com-junit/ http://qualidadebr.wordpress.com/2010/06/13/bdd-behavior-driven-development/ [...]
ASSINE NOSSO RSS




Pedro Bachiega
01. mar, 2009
Lucas, muito boa a idéia que vocês tiveram!
BDD sempre era um problema pela complexidade da maioria das ferramentas, mas vocês implementaram uma solução simples para resolver e é isso que importa!
Parabéns.
Grande abraço.
Alessandro Lazarotti
01. mar, 2009
Olá amigos!
Achei bem legal o que vocês fizeram com o jUnit, realmente ficou bem expressivo. Contudo recomendo que vocês olhassem/avaliassem uma ferramenta específica para vocês criarem as Specifications: http://www.concordion.org.
O Concordion faz um link extremamente simples entre as especificações e os testes, tornando esta tarefa bem produtiva e “frindly”. Dentro do Testcase do Concordion vocês usariam suas DSLs do Selenium da mesma forma que fizeram com o jUnit, só que sem perder tempo com a estrutura da Spec.
[]‘s
Alessandro Lazarotti
Germano
01. mar, 2009
Bem pensado, BDD com Java puro. Elegante
Porém não compreendi bem o último paragrafo, onde você comenta sobre ser fácil criar um gerador de relatório que analisa o arquivo da história. O que seria esse arquivo da história? o código Java de teste? Um exemplo de relatório assim poderia ser a execução dos testes pelo Maven?
Valeu!
Emerson Macedo
02. mar, 2009
Achei legal a idéia. Só acho que da forma que foi montado as classes Given When e Then vão ficar lotadas de métodos.
[]s
emerleite
Lucas Cavalcanti
02. mar, 2009
@Germano
Ficaria fácil gerar relatórios… é só tirar as palavras chave do java, os pontos e parenteses, e transformar camel case em palavras
public void login() {
given.iAmOnTheMainPage();
when.iLoginAs(“Lucas”);
then.myNameAppearsOnTheScreen(“Lucas”);
}
vira
Scenario: login
Given I am on the main page
When I login as Lucas
Then my name appears on the Screen Lucas
@Emerson
As classes podem ficar lotadas sim… mas daí você pode usar um trio (Given, When e Then) por pacote, ou por grupo de funcionalidades… não precisa existir só um trio no sistema.
Emerson Macedo
02. mar, 2009
@lucas
Sim, claro. Só que na estrutura montada teria que levar junto pra cada pacote a classe Story também correto? Acho que poderia dar uma mexida na arquitetura pra não precisar disso
Emerson Macedo
02. mar, 2009
De qualquer forma a idéia é ótima.
Germano
02. mar, 2009
Lucas, obrigado pela resposta!
Fácil mesmo gerar um relatório de cenários de testes dessa forma!
Gostei demais!
Phillip Calçado "Shoes"
05. mar, 2009
Este é o passo imediatamente anterior aos ‘story runners’, DSLs externas usadas por Cucumber e JBehave 2. Um comentário sobre esta prática:
http://fragmental.tw/2008/07/02/domain-driven-tests/
Mas eu teria um pouco de cuidado. O código deste exemplo é *bem* parecido -semanticamente- com o JBehave 1. É raro achar execplos em JBehave 1 mas no artigo clássico do Dan North você pode ver a semelhança:
http://dannorth.net/introducing-bdd
[]s
Rafael Noronha
22. mar, 2009
Lucas,
Bacana ver uma aplicação de bdd em Java.
Eu atuo com .net e após encontrar algums referências, cheguei ao resultado a seguir, aplicando o conceito com ajuda do projeto NBehave.
http://rafanoronha.net/testando-com-bdd/
Será que com Java conseguimos a mesma expressividade ? ; D
Abraços
Lucas Cavalcanti
23. mar, 2009
Rafael,
expressividade != legibilidade
a idéia é tornar o teste o mais legível possível, e nao o mais expressivo possível,
tanto que só usamos os recursos mais simples da linguagem: invocaçoes de métodos.
Bem interessante esse NBehave… mas você tem que se acostumar com esse
monte de parenteses e setas (nao conheço direito a sintaxe de .Net, talvez isso seja natural) e você fica preso ao framework…
Obrigado pelo comentário, vou dar uma olhada nesse NBehave pra ver se ele me dá idéias boas
[]‘s
Rafael Noronha
23. mar, 2009
Fala Lucas,
Pros desenvolvedores .net (desenvolvedores de verdade), não há nenhum segredo no código. Trata-se de expressões lambda, métodos anônimos que surgiram na época em que a MS lançou o LINQ.
Concordo com o foco na legibilidade do código, sem dúvidas é este o grande ganho do bdd, testar o comportamento do código (explicitamente as regras de negócio) da maneira mais fluente e inteligível possível.
Abraços
Douglas Hiura
20. abr, 2009
eu criei o meu, a base é o JUNIT e um pequeno arcabouço que criei, ainda tem compilador, mas ele só gera o teste do comportamento.
eu analisei de um ponto de vista mais amplo, em que um usuário (cliente,qualquer pessoa) ia poder descrever o cenário, então tive que estudar um linguagem simples e bem formada, o projeto consegue compilar uma estória e gerar o código que um pequeno arcabouço sobre JUnit e testar, é muito bom! mas não cheguei a terminar completamente o compilador. Não tive muito animo para terminar, porque só eu uso.
mas estou pesando em algum dia terminar, já que perdi dois dias de sol de Dezembro (podia ir pegar uma praia mole, joaca …), só pensando em arrumar o compilador.(o dia de codificar as idéias, vai se fude! vô pra praia … chegando na praia choveu).
Walter Longo
17. jan, 2010
Gostaria de saber se vcs ja fizeram algum teste com o framework Fitnesse. Achei interessante essa abordagem com JUnit, mas ainda acho que conforme o sistema for ficando grande vai ser complicado manter uma estrutura como essa.
[]‘s
Lucas Cavalcanti
17. jan, 2010
Olá Walter,
Eu já dei uma olhada no Fitnesse, mas não gostei mto, não lembro o motivo agora… nunca usei em projetos…
essa abordagem fica um pouco mais difícil quando o projeto cresce.. mas não tanto… Mas isso acontece com qualquer projeto usando qqer framework de testes…
no calopsita http://github.com/caelum/calopsita usamos essa abordagem, e conforme o projeto cresceu o máximo que aconteceu foi ter de refatorar o GivenContexts para separar um pouco os passos possíveis, mas continuou tudo funcionando do mesmo jeito
Eric Saboia
21. set, 2010
Muito interessante a idéia, simplificou bastante ao invés de procurar vários frameworks integrados pra resolver um problema (aliás, prática comum no java).
Só vejo um problema, imagina o tamanho que as classes GivenSteps, WhenSteps e ThenSteps terão após algumas estórias escritas… Imagino que uma implementação de cada uma das classes (estendendo as três genéricas) pra cada estória deixaria o código mais legível, apesar de burocratizar um pouco.
O que vocês acham?
Abraços e obrigado pelo compartilhamento