Caelum | Ensino e Inovação - Cursos de Java, Scrum, Ruby on Rails


Behavior Driven Development com JUnit

Por Lucas Cavalcanti em 28/02/09

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.

  • Share/Bookmark

Apostila reformulada! FJ-21: Java para desenvolvimento Web

Por Sérgio Lopes em 18/02/09

Acabamos de disponibilizar para download gratuito a nova apostila do curso FJ-21 Java para desenvolvimento Web.

Essa apostila está sendo reformulada desde o final do ano passado seguindo nossa política de constante atualização e evolução. Ela já está sendo usada nos cursos da Caelum desde o início do ano.

A apostila agora usa a última versão do Eclipse com WTP como ambiente de desenvolvimento. Além disso, tratamos das últimas versões do Struts 1.x, VRaptor, Hibernate 3 e Tomcat 6. Os textos foram revistos e ampliados, os exercícios estão mais completos, detalhados e com mais screenshots.

E, além disso, estamos usando o Tubaina para gerar essa apostila também, atingindo uma melhor diagramação e mais qualidade tipográfica através do LaTeX.

Aproveite e faça o download agora mesmo dessa nova versão da apostila! E esperamos comentários e feedbacks!

  • Share/Bookmark

Integração Continua – Builds rápidos com Grids e paralelismo

Por Lucas Cavalcanti em 09/02/09

Como já comentamos em um post anterior, aqui na Caelum fazemos a Integração Contínua das nossas aplicações, com a ajuda de algumas ferramentas, entre elas o Selenium, para os testes de integração das aplicações.

Um dos princípios de Integração Contínua é que, no final do processo de build, tenhamos um produto pronto pra ir pra produção. Para garantir que a aplicação está nesta situação, precisamos criar diversos tipos de testes automatizados, como os unitários e os de integração.

Testes de integração são aqueles que testam funcionalidades completas, ou seja, testam a integração de vários módulos do seu sistema para garantir que a funcionalidade desejada está completa. Em aplicações web, esse tipo de teste costuma envolver abertura de browsers e simulação da interação do usuário com o sistema.

O problema desses testes fazerem parte do build da aplicação, é que eles tendem a demorar demais à medida que a aplicação cresce, ferindo outro princípio da Integração Contínua: seu build deve ser o mais rápido possível. Não adianta nada ter um servidor de Integração Contínua, se seu build demora demais, por exemplo mais de 30 minutos. Imagine que você só descobre que a última mudança que enviou para o controle de versão (commit) quebrou alguns pontos de seu programa somente meia hora depois de iniciar uma nova tarefa. Sua mente, como desenvolvedor, já está muito longe do que executou até meia hora atrás e focada em um problema totalmente distinto.

Dez minutos é um bom limitante superior pro tempo do seu build, fazendo com que o feedback seja rápido, assim um build quebrado tende a ser corrigido imediatamente, e a aplicação a estar sempre no estado pronto pra deploy o tempo todo.

Mas como fazer um build de menos de dez minutos, quando só os meus testes de integração demoram mais de meia hora? Uma alternativa é tirá-los do processo de build, e rodá-lo só no fim do dia, ou a cada hora. O problema é que perdemos o feedback rápido dos testes de integração, caindo em uma situação ainda pior que a citada acima, com o feedback de 30 minutos.

Outra alternativa, bem mais interessante, é rodar seus testes de integração em paralelo. Aqui na Caelum usamos algumas ferramentas para conseguir rodar os testes dessa maneira. A primeira delas é o Parallel Junit, capaz de rodar em paralelo um conjunto qualquer de testes compatíveis com JUnit. Para aqueles que usam o TestNG, existe um suporte natural à essa funcionalidade, que permite somente passar parâmetros na sua tag do ant ou maven.

De qualquer maneira, não adianta rodar somente uma instância do Selenium e tentar rodar os testes em paralelo. Então usamos o Selenium Grid, que permite subir mais de uma instância do Selenium Server na mesma máquina, ou melhor ainda, em várias máquinas, deixando isso transparente para os testes que usam o Selenium. De uma maneira ainda mais emocionate, podemos levantar diversos browsers distintos em máquinas com sistemas operacionais diferentes, possibilitando rodar os testes em paralelo em ambientes como Windows e Linux.

No Selenium Grid temos duas partes importantes: O Selenium Hub, que vai responder aos comandos do selenium emitidos pela sua aplicação, e delegá-los para algum dos Remote Controls, que executarão os mesmos no selenium de verdade. Em outras palavras, o Hub funciona como um proxy para cada comando que o teste deseja executar no browser, delegando essa requisição para algum Remote Control ocioso.

Um grande problema do Selenium Grid é a sua usabilidade. Para configurá-lo, precisamos subir o Selenium Hub em uma máquina, depois ir em cada máquina que rodará os Remote Controls, e subir um por um na mão, usando uma task pronta do Ant. Chato? Mas ainda não é o maior problema…

Se a máquina que tem o Hub cair, precisamos ir em todas as máquinas que tem os Remotes, e registrar tudo novamente. Se uma máquina que tem os Remotes cair sem executar um shutdown limpo, não desregistrando seus remotes, precisamos reiniciar tudo de novo, pois se um teste tentar usar o Remote “fantasma”, vai dar erro. Por fim, se um teste abrir um Selenium, e esquecer de fechar, o Remote Control associado a esse Selenium ficará travado para sempre, e então você terá que reiniciar todos os programas novamente.

Por causa disso, eu e o Guilherme Silveira resolvemos hackear o Selenium Grid e resolver esses problemas. Criamos então um fork do projeto original no github, e um novo projeto, o Selenium Box Agent.

A idéia desse novo projeto é a seguinte: Em cada máquina onde serão rodados os Remote Controls, deixamos uma instância do Box Agent rodando. Na máquina onde rodará o Hub, rodamos o projeto do Grid modificado. E então é só ir na página de console do Hub (geralmente em http://localhost:4444/console, substituindo localhost pelo ip do seu servidor) e registrar os Box Agents, pedindo para iniciar os Remotes na mesma página, sem necessidade de linha de comando, tasks do ant, nem qualquer outro trabalho manual e repetitivo. Apenas formulários e links na página do console.

Página Principal do Hub modificado

Página Principal do Hub modificado


box adicionado

Box adicionado


Remote Control Adicionado

Remote Control Adicionado

Se a máquina do Hub cair, quando ela voltar levantará automaticamente os Remotes de todos os Boxes registrados. Se uma máquina de Box cair, o Hub desregistrará automáticamente todos os seus Remote Controls, e quando ela voltar o Hub registra todos eles automaticamente também. Se um teste esquecer de devolver um Selenium, você pode desregistrar o RemoteControl associado, e pedir pra registrá-lo novamente, direto da página de console.

Um dos nossos builds, que demorava cerca de 50 minutos quando os testes eram rodados serialmente, demora por volta de 8 minutos quando rodado em paralelo, usando 7 threads simultâneas de testes. Se a aplicação crescer mais, é só registrar mais Remote Controls e aumentar o número de Testes simultâneos. E pronto, conseguimos o build de dez minutos, sem sacrificar os testes de integração.

O resultado? Uma equipe mais propensa a corrigir seus próprios erros a medida que eles são cometidos, sem ter que esperar o feedback de um cliente, possivelmente furioso, sobre algo que foi quebrado na última entrega.

Aqui na Caelum tentamos sempre encontrar alguns projetos com os quais podemos contribuir, seja com comentários, documentação ou código e ter sido capazes de criar uma extensão para o Selenium Hub é mais uma maneira que encontramos para compartilhar algo criado por nós com aqueles que procuram e precisam de uma solução similar.

  • Share/Bookmark



Caelum | Ensino e Inovação
São Paulo: Rua Vergueiro, 3185, cj. 87, próximo ao Metrô Vila Mariana   |   Tel. (11) 5571-2751
Rio de Janeiro: Rua Senador Dantas, 80, cj. 307/308 - Centro   |   Tel. (21) 2220-4156 ou 2297-0033
Brasília: SCS Qd. 8 Bl. B-50, Sala 521 - Ed. Venâncio 2000   |   Tel. (61) 3039-4222