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.