Test Driven Development, ou Behaviour Driven Development… duas “buzzwords” que, só de serem pronunciadas, trazem milhares de sentimentos. Porém, isso não mostra uma cruel realidade:

Poucas pessoas concordam com o que é, de fato, “teste de software”.

Em uma apresentação que fui, no AgileBrazil, com cerca de 150 pessoas na platéia, o apresentador lançou a pergunta: “o que é teste de software para você?”. O experimento era escrever em um papel sua resposta, e trocar com a pessoa do lado. Estranhamente, apenas 5 pessoas, na platéia toda, receberam um papel com uma explicação que batia com a sua.

Então, acho que seria interessante citar o que eu entendo por Teste de Software, e porque eu considero que pratico “Behaviour Driven Development”.

Para mim, Teste de Software é qualquer coisa que garanta que um fragmento de código faça o que ele diz que faz. Se há um fragmento de código tal como:

def somar(n1, n2)
  return n1 + n2
end

Deve haver algo que valide que aquele método, “somar”, soma dois números e retorna o resultado. Pode ser qualquer coisa:

  somar(1, 2).should be(3)
  assert(somar(1, 2), 3)
  expect(somar(1, 2)).toEqual(3)
  raise "Erro de Validação" unless somar(1, 2) == 3

fill_in :n1, :with => 1
  fill_in :n1, :with => 2
  click_button 'Somar'
  body.should match(/3/)

Certo, maravilha. Isso é um teste de software. Porém, para mim, teste me lembra um pouco do método científico: para que uma hipótese seja confirmada, deve ser simples de reproduzí-la, e deve ser muito explícita a premissa final. Por exemplo, imaginemos agora um sistema mais ou menos assim: ele busca de um banco de dados as informações de pessoas, e cria uma página separando os homens das mulheres. Usando uma gem como “FactoryGirl” para gerar dados de teste, teríamos um código de teste semelhante ao seguinte:

it 'should return a structure where men and women are separated' do
  Factory :person, :name => "Man's Name", :sex => 'M'
  Factory :person, :name => "Woman's Name", :sex => 'F'
  page = create_page
  page = Nokogiri.parse(page)
  men = page.search('//fieldset[legend="Men"]')[0].inner_html
  women = page.search('//fieldset[legend="Women"]')[0].inner_html
  men.should match(/Man's Name/)
  women.should match(/Woman's Name/)
end

Certo… e se precisássemos escrever outro código, agora, muito semelhante mas para testar outra coisa? Só nesse exemplo simples, temos 8 linhas de código para testar o sistema. E se, depois de escrever uns 10 testes, todos eles levando em consideração a página, fosse definido que é mais fácil usar “div” ao invés de “fieldset/legend” para separar os homens das mulheres? Quantas mudanças seriam necessárias?

Enfim, o que eu faço é deixar certas coisas menos explícitas. Por exemplo, qual é o comportamento que eu espero? Eu espero que eu tenha uma página aonde, em algum canto, estejam todos os homens, e em outro, todas as mulheres, e que nesse canto dos homens, exista um nome chamado “Man’s Name”.

Outro aspecto, eu não gosto de “before” e “after” para definir um estado. Eu gosto de ser explícito sobre qual é o “setup” que eu estou fazendo para deixar o meu programa no jeito, pronto pra testar. Logo, o código acima seria:

it 'should return a structure where men and women are separated' do
  create_people
  page = create_page

#Aqui várias coisas podem ser pensadas: usar dois métodos:
  men_on(page).should match(/Man's Name/)
  women_on(page).should match(/Woman's Name/)

#Usar só um método:
  all(:men, :on => page).should match(/Man's Name/)
  all(:woman, :on => page).should match(/Woman's Name/)

#Usar um "matcher":
  page.should have_man("Man's Name")
  page.should have_woman("Woman's Name")
end

Quatro linhas. Sem lidar com “XPath” manualmente ou qualquer coisa. Vantagens são muitas, o seu código fica mais “declarativo”: meio que você explica o que está acontecendo, ao invés de deixar coisas “sub-entendidas” (como usar um “before” para criar as pessoas, removendo o “create_people”), mas ao mesmo tempo há coisas sub-entendidas também (a lógica para buscar, na página, qual fragmento se refere aos homens e qual às mulheres). Há dois ganhos aqui muito poderosos: o primeiro, é que o código fica legível: todo aquele esquema do Nokogiri, do Factory, torna muito difícil entender exatamente o que está acontecendo de fato. Porque usamos o Nokogiri, pra que ele precisa estar lá? Qual a utilidade daquele “search”, e do [0], e do “inner_html”? Se der um erro, o que significa “undefined method ‘inner_html’ for NilClass”?

O segundo ganho é a facilidade de escrever novos testes, e pra mim esse é o maior ganho de todos. Com mais quatro linhas, você escreve outro teste qualquer. É simples, intuitivo, quase um “copy-paste”. E pode ser a diferença entre entender ou não o que um teste está exatamente testando, como esse exemplo real que enfrentei no meu trabalho outro dia: transformar isso:

it 'deve atualizar os dados de uma turma na importação' do
  Factory :periodo
  turma = Factory :disciplina 
  total_novas_vagas = turma.vagas + 10
  nome_novo = turma.nome + " novo"
  t_novo = turma.carga_horaria_teorica + 1
  horarios = [{"semana"=>1, "inicio"=>"08:00", "fim"=>"14:00"}, {"semana"=>1, "inicio"=>"08:00", "fim"=>"14:00"}]
  turmas = [{"nome" => nome_novo,
            "vagas" => total_novas_vagas,
            "carga_horaria_pratica" => turma.carga_horaria_pratica, 
            "carga_horaria_teorica" => t_novo,
            "carga_horaria_individual" => turma.carga_horaria_individual,
            "id" => turma.id,
            "codigo" => turma.codigo,
            "campus_id" => 4,
            "id_turma_sie" => 1,
            "horarios" => horarios}]

RestClient.stub!(:get).with(url_rest("disciplinas_matricula/#{@ano}/#{@periodo}.xml")).and_return(turmas.to_xml(:root => 'turmas'))
  post :atualizar_turmas
  turma = Disciplina.find turma.id
  turma.vagas.should == total_novas_vagas
  turma.nome.should == nome_novo
  turma.carga_horaria_teorica.should == t_novo
  turma.horarios.should == horarios = [{"semana"=>1, "inicio"=>"08:00", "fim"=>"14:00"}]
  response.should redirect_to(disciplinas_path)
end

Nisso:

it 'deve atualizar os dados de uma turma na importação' do
  disciplina = Factory(:disciplina)
  turma = disciplina.attributes
  turma['vagas'] += 10
  turma['nome'] += " novo"
  turma['carga_horaria_teorica'] += 1

stub_get_turmas.and_return(xml_disciplina :disciplina => turma)
  post :atualizar_turmas
  disciplina.reload
  disciplina.vagas.should == turma[:vagas]
  disciplina.nome.should == turma[:nome]
  disciplina.carga_horaria_teorica.should == turma[:carga_horaria_teorica]
end

O que torna o código mais simples, mais óbvio, e principalmente, facilita muito o entendimento e a criação de novos testes.