BDD com Rails e Cucumber

03.Novembro.2009
Cucumber é um framework de BDD para Rails que permite que sejam escritos testes de maneira muito próxima à linguagem comum do cliente, ou seja, um mesmo documento de testes pode ser compreendido facilmente pelo cliente e pelo desenvolvedor, com o mínimo possível de ambiguidade e ruido.

Para instalar, considerando obviamente que você já tem o Rails instalado, digite

gem sources -a http://gems.github.com
sudo gem install cucumber webrat rspec rspec-rails

No diretório raiz do seu projeto, digite

script/generate cucumber --rspec

E o suporte ao Cucumber será adicionado à sua aplicação, assim como uma pasta /features.

Na pasta features, cria um arquivo com extensão .feature. Por exemplo, manage_users.feature.

Vamos editar o arquivo e começar com a definição da funcionalidade (ou user story, se preferir):

Feature: Manage users
    In order to manage users
    Visitors should be able to
    sign up and change their own stuff

Atenção para a tabulação. Ela é fundamental para o bom funcionamento da ferramenta.

Em seguida, vamos adicionar os cenários possíveis que se aplicam a essa funcionalidade. Nesse caso podemos pensar nas situações que podem ocorrer para gerenciarmos usuários. Futuramente podemos adicionar mais situações que não foram previstas inicialmente e situações que estão causando comportamentos inesperados na sua aplicação.

  Scenario: Add new user
      Given the Visitor wants to signup
      When the Visitor clicks signup
      Then open the signup page

Novamente, atenção para o espaçamento no início de cada linha.

Execute o comando cucumber e você vai ver uma saída parecida com essa:

Feature: Manage users
    In order to manage users
    Visitors should be able to
    sign up and change their own stuff

  Scenario: Add new user              # features/manage_users.feature:6
    Given the Visitor wants to signup # features/manage_users.feature:7
    When the Visitor clicks signup    # features/manage_users.feature:8
    Then open the signup page         # features/manage_users.feature:9
1 scenario (1 undefined)
3 steps (3 undefined)
0m0.297s

You can implement step definitions for undefined steps with these snippets:
Given /^the Visitor wants to signup$/ do
  pending
end

When /^the Visitor clicks signup$/ do
  pending
end

Then /^open the signup page$/ do
  pending
end

O texto em amarelo significa que o teste (step na documentação do Cucumber) ainda não foi implementado.

Dentro da pasta features, tems uma chamada step_definitions. Nessa pasta vamos criar um arquivo manage_users.rb, copie o primeiro trecho em amarelo (Given … end) e cole no arquivo. Apague a linha onde está escrito pending e escreva seu código de teste.
Ele vai ficar assim:

Given /^the Visitor wants to signup$/ do
  #simula a visita do cliente à página principal, onde temos um link escrito "Quero me cadastrar"
  get '/'
end

Ao executar cucumber novamente, veremos a seguinte saída:

(...)
  Scenario: Add new user              # features/manage_users.feature:6
    Given the Visitor wants to signup # features/manage_users.feature:7
    When the Visitor clicks signup    # features/manage_users.feature:8
    Then open the signup page         # features/manage_users.feature:9

Vemos a linha do step que escrevemos na cor verde, ou seja, o teste passou.

O modo correto de se trabalhar com Cucumber é, assim como em TDD, escrever o teste, dentro de um step, executar e vê-lo falhar. Ao falhar, a linha será apresentada em vermelho. Programe somente o necessário para o teste passar (ficar verde), e passe imediatamente para o próximo step.

Existem formas de se trabalhar com Cucumber em outros idiomas, gerar documentação em HTML e PDF e outros macetes, mas isso vai ficar para um próximo post.

Enjoy ;-)


Melhorando a expressividade dos testes unitários com JUnitXtension – Parte 2

26.Outubro.2009
Na Parte 1 eu apresentei a motivação da biblioteca JUnitXtension. Recomendo a leitura.

Por que assertNotEquals ao invés de assertDifferent?

Eu poderia ter usado assertDifferent ao invés de assertNotEquals, mas isso poderia nos levar a um problema semântico. Em Java, == (operador de comparação de igualdade) tem um comportamento diferente de equals (método utilizado para checar a igualdade de valores).

No caso de uma String, por exemplo, "foo" == "foo", ou seja, uma String é sempre igual a ela mesma. Porém, se tivermos uma variável foo, contendo “foobar” e outra bar, também contendo “foobar”, estaremos falando de variáveis diferentes, posicionadas em locais diferentes da memória e, na maioria das vezes, com valores também armazenados em endereços diferentes, fazendo com que o uso do operador == retorne falso, mesmo quando ambas contém o valor “foobar”.

Por conta de situações como essa, os objetos Java contém o método equals, que verifica se o conteúdo é igual, e não o endereço onde o conteúdo está guardado. Usando o exemplo do parágrafo anterior, foo.equals(bar) retornaria verdadeiro.

Sendo assim, para comparação entre objetos, assertDifferent deveria checar se o objeto informado é exatamente o mesmo do objeto esperado, o que até hoje ainda não vi acontecer. assertEquals checa se o valor da objeto informado é igual ao valor esperado, o que respeita a idéia inicial de uma asserção.

Por outro lado, em Java existe o conceito de tipos primitivos, que são números, bytes, caracteres e valores booleanos (true, false). Como um tipo primitivo não é um objeto, não existe a preocupação de termos um mesmo valor em endereços de memória diferentes, então uma comparação entre foo=42 e bar=42 é sempre verdadeira. Nesse caso sim o uso de assertDifferent faria sentido, mas causaria uma bela confusão por causa da sujeira que mais e mais asserções redundantes traria. Quando tratamos de números, podemos tanto dizer que 42 é diferente de 0 como podemos dizer também que 42 não é igual a 0. Então fica o assertNotEquals e não se fala mais nisso =)

Se interessou pela idéia? Você encontra os mesmos problemas e quer conhecer mais? A biblioteca está na versão 0.1.1 e pode ser encontrada no Google Code. Críticas construtivas são sempre bem vindas.

Keep testing =)

P.S.: Agradecimentos públicos à minha esposa e ao Fábio Serra pelas revisões nesse post


Melhorando a expressividade dos testes unitários com JUnitXtension – Parte 1

23.Outubro.2009
Qual dos códigos é mais simples de ler?

Código A

assertFalse(foo.equals(bar));

assertFalse(foobar == 2);

Código B

assertNotEquals(foo, bar);

assertNotEquals(foobar, 2);

Inicialmente criado apenas como um projeto para me auxiliar no trabalho a escrever testes mais simples de ler, resolvi compartilhar no Google Code a biblioteca, atualmente na versão 0.1.1.

A principal motivação foi de criar novas asserções que melhorem a expressividade, mesmo que redundantes, para que o desenvolvedor não tenha que escrever códigos auxiliares limitados à API padrão do JUnit.

Internamente o próprio JUnit trabalha dessa maneira. Praticamente todas as demais asserções são abstrações sobre assertTrue, no máximo adicionando alguma formatação diferenciada para que o resultado e um teste falho seja legível.

Ao invés de, por exemplo, escrevermos assertFalse(0 == 1), podemos escrever assertNotFalse(0, 1). Ao invés de assertTrue(x > 0), podemos simplesmente escrever assertGreaterThanZero(x) e assim por diante.

Outra vantagem diz respeito à cobertura de testes. Em alguns casos, quando se usa assertFalse(x == 0), o algoritmo que calcula a cobertura vai informar que essa linha foi apenas parcialmente testada. Na verdade queremos apenas testar se x é igual a zero, mas se você persegue os 100% de cobertura, esse tipo de problema obriga a criar código extra apenas para manter a cobertura do código satisfatória. Utilizando assertNotEquals(0, x), ou o teste passa ou não passa, reduzindo para apenas duas as opções possíveis, limpando o código e aumentando automagicamente a cobertura.

Se interessou pela idéia? Você encontra os mesmos problemas e quer conhecer mais? A biblioteca está na versão 0.1.1 e pode ser encontrada no Google Code. Críticas construtivas são sempre bem vindas.

Keep testing ;-)

P.S.: Agradecimentos públicos à minha esposa e ao Fábio Serra pelas revisões nesse post


Detox

21.Agosto.2009
Desenvolvedor também tem vida pessoal.

Finalmente tirei férias e vou aproveitar o tempo livre para casar, ter um(a) filho(a), tomar um pouco de sol, escrever um artigo, montar uma palestra e, se sobrar tempo, escrever uma aplicação de OCR só pelo tesão de programar.

Bom final de Agosto e bom início de Setembro a todos e lembrem-se, crianças: Winners write tests ;-)


RESTful Rails automático

02.Julho.2009
Solução OO para fazer sua aplicação Rails trabalhar de modo RESTful:

application_controller.rb:

  def index
    begin
      if request.post?
        create
      elsif request.get?
        read
      elsif request.put?
        update
      elsif request.delete?
        delete
      end
    rescue NameError
      respond_to do |format|
        format.html {render :text => "<h1>Forbidden</h1>", :status => 403}
      end
    end
  end

E no arquivo routes.rb:

  map.connect ':controller/:id'
  # map.connect ':controller/:action/:id'

Como funciona?

Uma aplicaçao Rails RESTful funciona, basicamente, utilizando o método HTTP como verbo e a URI como substantivo.

Ao invés de algo do tipo http://application:3000/user/view/1, com REST você usa http://application:3000/user/1 para pegar os dados, atualizar ou apagar, mudando apenas o método HTTP da chamada.

Porém, por padrão, o Rails entende as requisições como sendo /controller/action/id, por isso a alteração no arquivo routes.rb, para fazer com que o segundo parâmetro seja o ID, já que quem define a action é o próprio método HTTP.

Dependendo do método utilizado, será executada uma action padronizada que será escrita no seu próprio controller, usando o conceito de sobrescrita de métodos. Caso você não escreva qualquer uma das actions, será exibida uma mensagem de erro padrão, permitindo que você foque somente no desenvolvimento do que for necessário.


Scott Adams was right

03.Junho.2009
dilbert2

De volta ao Brasil, e de volta à programação normal.

A propósito, um desafio geek. Refatore o código abaixo:

do {
} while (window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && scan < strend);

Simples :P


Choque cultural

27.Maio.2009
Diferenças culturais são bem difíceis de conciliar quando você trabalha em outro país, com outros povos. E sob pressão isso só tende a piorar.

Rated R

26.Maio.2009

“Fazer refactoring sem testes é o mesmo que pegar puta no calçadão e depois dizer ‘eu não sabia’ quando as coisas não saem como esperado.”

Pensamento antigo, mas válido.


Viajando de novo

18.Maio.2009
Estou na prosaica cidade de Santiago de León de Caracas, capital da Republica Bolivariana da Venezuela. Sim, povo aqui é megalomaníaco.

Mais duas semanas sem postar, sem dormir e sem comer direito.

E há quem pense que eu viajo para ficar esquiando por aí.


Entrevista de emprego

15.Maio.2009

dilbert-1

Há seis meses fiz uma entrevista de emprego totalmente improvisada e despreparada. O carinha não entrou na empresa, o que não considero necessariamente negativo, e eu não participei de nenhuma outra entrevista de emprego, o que foi muito positivo. Para ser bem honesto, eu nem mesmo acho que o resultado daquela entrevista serviu pra alguma coisa.

As vezes, mais como um exercício mental, eu penso como seria uma entrevista de emprego para minha empresa fictícia, para uma vaga de desenvolvedor. Vamos supor que seja para trabalhar com Java, que é meu atual ambiente natural.

Primeiro aquela olhada no currículo. O cara tem meia dúzia de certificações, um emprego numa fábrica de software conhecida, formado numa faculdade da moda e chega de gravata para a entrevista. Sem julgamentos precipitados, lembre-se que esse é o perfil padrão de qualquer entrevista de emprego. Pode apostar, pelo menos um candidato vai aparecer nesses moldes. Numa empresa comum, ele estaria muito bem cotado a entrar.

Mas vamos à parte que interessa.

Antes de aplicar qualquer prova escrita, vou fazer algumas perguntas. Poucas, e em português mesmo. Considere que a entrevista é um momento tenso por natureza, e que não vai ajudar em nada piorar essa situação. Lembre-se de que a vaga é para desenvolvedor Java, e não para atirador de elite da SWAT.

Você gosta de ler? Qual a sua média de livros técnicos por mês? Você dá preferência a livros traduzidos ou em inglês?

Caso responda ‘não’ à primeira pergunta, podemos encerrar a entrevista por aqui mesmo. Preferência por livros traduzidos ao invés de livros em inglês, para mim, pode denotar preguiça e comodismo. Mas não acredito que esse ponto seja fundamental para definir a qualidade do profissional nesse ponto da entrevista.

Você já leu “Effective Java” (Joshua Bloch), “Test-Driven Development” (Kent Beck) e “Refactoring” (Martin Fowler)? O candidato ganha bônus se também já leu “Practices of an Agile Developer” (Venkat Subramaniam e Andy Hunt), “The Art of UNIX Programming” (Eric Raymond). Claro, pergunte também o que ele pensa dos livros e, principalmente, em que pontos ele discorda.

Eu considero os três primeiros leituras obrigatórias para qualquer programador que se considere profissional. Os seguintes são leituras importantes que não podem ser ignoradas. Se bem que, atualmente, se eu entrevistar um sujeito que realmente leu e consiga levar uma discussão rápida sobre todos eles, é bem provável que eu encerre a entrevista ali mesmo e o contrate de imediato. Novamente, se o candidato não leu nenhum deles, podemos encerrar por aqui para evitar mais perda de tempo.

O candidato conhece o Manifesto Ágil? Não estamos cobrando aqui nenhuma certificação-papel-de-bobo de Scrum ou coisa parecida (acalmem-se trolls, eu também tenho uma). Também não quero que o cara tenha o Manifesto decorado na ponta da língua. Importante é conhecê-lo e entender o que significa. Conhece TDD (ok, mesmo lendo o livro) e consegue usar essa abordagem de forma pragmática? Por pragmática entenda-se: sim, você tem que escrever testes antes de codificar. Não, você não pode perder tempo tentando alcançar utópicos 100% de cobertura de código. A tarefa do programador não é entregar as Obras Completas de Shakespeare em Java, mas sim código de qualidade em tempo relativamente curto. Utópico, mas que entrevista de emprego não é?

O candidato tem algum pet-project? Participa ou participou de algum projeto open source? (tradução de arquivos de i18n não conta). Programa porque gosta ou apenas porque alguém disse que dava dinheiro? Estuda tecnologias, linguagens ou abordagens que não estão necessariamente relacionadas ao trabalho? Conhecer Lisp, Erlang, Haskell e/ou linguagens dinâmicas ajuda a enxergar maneiras novas e até mesmo mais simples e limpas de se resolver um mesmo problema.

Na seqüência, se ainda não foi encaminhado à porta de saída, o candidato deve escrever uma dissertação breve, de vinte linhas, sobre algum assunto relacionado à área. A idéia aqui é apenas avaliar a clareza de raciocínio ao passar idéias para o papel e evitar futuros problemas com analfabetos funcionais. Seco e direto assim.

Agora a sobremesa. A hora preferida do verdadeiro desenvolvedor: a prova prática.

O candidato deve resolver um code kata em tempo pré-determinado (aqui a pressão faz parte do jogo), previamente definido e exibido todo o tempo. Um relógio na parede ajudaria nesse ponto. O desenvolvedor deve conviver com prazos definidos, que geralmente são curtos, e pressão. Nada de anormal. É exigido que o candidato utilize TDD ao resolver. Se ele chegou até aqui, pode apostar que ele consegue.

Ah sim. Arranje uma máquina decente com um ambiente semelhante ao real para que ele faça essa parte da entrevista. Codificar em folha de papel é uma das coisas mais ridículas de uma entrevista. Só perde para dinâmica de grupo e entrevista com psicóloga (note o gênero).

Avalie a expressividade do código. Isso é importante. Você não vai querer pegar um código suíço pela frente, cheio de variáveis enigmáticas e abreviações que não querem dizer nada.

Como eu disse no início desse texto, que saiu mais longo do que eu esperava, isso é apenas um exercício mental. Mas no momento está me parecendo um modo bem mais honesto e efetivo do que passar um dia inteiro numa empresa escrevendo redações sobre minhas férias, sendo entrevistado por psicólogas (de novo o gênero) que mal sabem o que a empresa faz, e para terminar, com fome e dor de cabeça, uma entrevista técnica com um sujeito que não acha a própria bunda usando as duas mãos (do tipo que briga com você jurando de pés juntos que C# aceita herança múltipla). Ouviu, sevencomm?

Bom fim de semana,