Behavior Driven Development com JUnit

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.

21 Comentários

  1. Pedro Bachiega 01/03/2009 at 13:59 #

    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.

  2. Alessandro Lazarotti 01/03/2009 at 15:44 #

    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

  3. Germano 01/03/2009 at 23:56 #

    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!

  4. Emerson Macedo 02/03/2009 at 06:24 #

    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

  5. Lucas Cavalcanti 02/03/2009 at 08:22 #

    @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.

  6. Emerson Macedo 02/03/2009 at 09:06 #

    @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 😉

  7. Emerson Macedo 02/03/2009 at 09:06 #

    De qualquer forma a idéia é ótima.

  8. Germano 02/03/2009 at 13:56 #

    Lucas, obrigado pela resposta!
    Fácil mesmo gerar um relatório de cenários de testes dessa forma!
    Gostei demais! 🙂

  9. Phillip Calçado "Shoes" 05/03/2009 at 22:59 #

    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

  10. Rafael Noronha 22/03/2009 at 09:21 #

    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

  11. Lucas Cavalcanti 23/03/2009 at 00:16 #

    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

  12. Rafael Noronha 23/03/2009 at 10:51 #

    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

  13. Douglas Hiura 20/04/2009 at 14:40 #

    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).

  14. Walter Longo 17/01/2010 at 18:45 #

    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

  15. Lucas Cavalcanti 17/01/2010 at 20:59 #

    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

  16. Eric Saboia 21/09/2010 at 14:34 #

    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 😉

Deixe uma resposta