Posts Tagged ‘documentacao executavel’

Especificações, código e como mantê-los unidos?

Posted in ruby, Ruby para iniciantes, testes on August 18th, 2010 by Sergio Azevedo – 4 Comments

Já faz algum tempo que testes de unidade, ou unitários, tem sido assunto frequente, e as vezes acalorado, em listas de discussões. Apesar de fazer tempo que este assunto é discutido o argumento mais famoso para não adoção da técnica continua sendo o curto prazo dos projetos. Alega-se que se os desenvolvedores começarem a escrever testes, os prazos para realizações das tarefas serão aumentados, o que consequentemente aumentaria o prazo final do projeto.

Aquela velha historia

A maioria dos projetos, tem uma documentação inicial, onde são colocadas as necessidades do cliente. Num projeto tradicional, ou não, esta documentação será entregue aos desenvolvedores para fazer o milgre de transformar texto impresso no papel, em software. Até agora não contei novidade alguma, mas a coisa vai ficar mais interessante a partir de agora.

Os desenvolvedores começam então, a escrever o software baseado nas informações contidas na documentação entregue a eles. Agora imagine que boa parte do software já está implementada e o gerente de proejetos está super feliz em ver o gráfico do project bonitinho, mostrando que o projeto está até adiantado. Quando… acontece o que sempre acontece em projetos de software. Uma grande mudança, uma alteração tão radical que afetará varios pontos do sistema. Vocês já viram este filme, e sabem que numa situação dessas é comum ter que fazer tudo pra ontem e sem prejuizo ao cronograma. O que se traduz em horas extras e trabalho nos finais de semana. O ambiente de trabalho legal para os desenvolvedores costuma acabar neste ponto. Como as alterações são todas urgentes, é muito comum que a equipe concentre forças na alteração do código fonte, enquanto a alteração da documentação fica pra depois. Um depois que geralmente nunca acontece.

TDD e Documentos… onde você quer chegar?

Pronto agora cheguei onde queria =). Comecei falando de TDD e no paragrafo anterior falei de documentação desatualizada. Que relação essas coisas podem ter?

Bem, a documentação incial do projeto, em papel, descreve o que o software deve fazer (comportamento), os métodos das classes escritas pelos desenvolvedores são responsáveis por realizar este comportamento. Em um cenário, como posso dizer, “mais comum” depois do desenvolvedor implementar o software, uma pessoa seria encarregada de testar o comportamento do software, para isso ela precisaria recorrer a documentação do projeto. Mas depois da mudança a documentação vai estar desatualizada o que abre margem para problemas maiores.

Uma maneira de resolver este problema seria automatizando os testes, e neste ponto entram os testes de unidade. Imagine que nossos desenvolvedores escreveram testes de unidade automatizados para checar o compotamento de cada um dos métodos implemetados por eles. Agora não precisarimos de mais uma pessoa para testar, e o mais importante, agora temos uma documentação executável. Isso mesmo pense nos testes de unidade como sendo uma documentação executável, uma documentação que quebra se não for atualizada.

Documentação Executável

Como todos sabem a documentação mais atualizada de um software é o código fonte. O problema é que ele não é “amigavel e legivel” para todos.  Pode ser pouco agradavel ter de ficar olhando varios arquivos, classes e métodos para entender o que eles fazem realmente. Na tentativa de melhorar, esse cenário pessoas passaram a utilizar uma técnica para escrita de testes que tenta expressar mais a intenção daquele comportamento. Esta técnica é conhecida como Behaviour Driven Development (BDD), foi um termo cunhado por Dan North mais informações aqui. Mas continua sendo muito código, e fica ainda complicado identificar o que é uma pré-condição, o que realmente está sendo testado é o que é a pós-condição. No JUnit por exemplo, a responsabilidade de descrever o comportamento testado fica no nome do método, que pode ficar quilometrico (nada contra), como por exemplo: deveSerCapazDeCriarUmaListaDePedidosAPartirDoXML.

Na tentativa de tornar os comportamentos mais evidentes, sugriam framework’s como RSpec utilizado por desenvolvedores ruby.
Veja abaixo um exemplo de código do RSpec

describe "Uma Conta"
it "nao deve permitir saques superiores ao valor de seu saldo" do
   conta = Conta.new
   conta.deposita 100
   lambda{ conta.saca 200}.should raise_error ArgumentError
end

Apesar de ter menos “ruido”, muito também devido a linguagem ruby,  este código ainda tem muito apelo programático. Fica dificil usar um negocio desses com seu cliente, ou especialista de negocios. Então o bacana mesmo seria escrever especificações/documentação, usando o bom e velho português, e ter algum vodoo que tornasse essa documentação executável.

E esse Vodoo existe?

Sim esse vodoo existe e se chama Cucumber.
O Cucumber é um framework que permite a escrita de funcionalidades, no estilo user story, uma especie de caso de uso. Vejamos um exemplo:

#language: pt
Funcionalidade: Saque de conta
   Para retirar dinheiro do banco
   Como um correntista do banco
   Devo realizar um saque
 
Cenario: Saque com sucesso
   Dado que a "minha conta" possui "saldo" de "100" reais
   Quando eu sacar "70" reais da "minha conta"
   Entao "minha conta" devera ter "saldo" de "30" reais
 
Cenario: Saldo Insuficiente para Saque
   Dado que a "minha conta" possui "saldo" de "100" reais
   Quando eu sacar "200" reais da "minha conta"
   Entao um erro de "Saldo Insuficiente" deve ser exibido

Pra começar as historias descrevem funcionalidades (features), e é comum se apresentarem neste formato:
Para alguma coisa
Como um papel,
Devo fazer alguma coisa.
Logo depois definimos os cenários, nosso exemplo tem dois cenários um de sucesso e outro de falha, você pode escrever quantos cenários quiser.

Aham Cláudia senta lá…

Então agora eu escrevo a especificação que eu quiser usando português e pronto vai funcionar? Quase isso, o cucumber permite o uso do português para descrevermos a funcionalidade e seus cenários, mas lembre-se que no fundo no fundo tudo isso é um grande teste. A grande diferença é que o apelo programático é retirado, mas a intenção do teste ainda está lá. O JUnit nos fornece os chamados TestCase para que possamos agrupar nossos testes. O cucumber nos fornece o recurso de features (funcionalides) no qual descrevemos uma funcionalidade. Cada funcionalidade do sistema costuma ser descrita por em um arquivo separado que possui a extensão .feature, o exemplo da funcionalide saque de conta ficaria num arquivo chamado: saque_conta.feature. Além disso o cucumber impõe que estes arquivos “vivam” dentro de uma pasta que se chame “features”. As features podem conter vários cenários. Dá pra fazer um paralelo entre os cenários e os métodos de teste de um TestCase do JUnit. Os cenários descrevem de maneira mais intuitiva os 3 componentes do teste: pré-condição, teste e pós-condição, como podemos ver abaixo:

Cenario: Saque com sucesso
   Dado que a "minha conta" possui "saldo" de "100" reais #pré-condição
   Quando eu sacar "70" reais da "minha conta"             #teste
   Entao "minha conta" devera ter "saldo" de "30" reais  #pós-condição

A primeira linha define a pré-condição a segunda deixa claro o que queremos testar e a útlima linha checa a pós-condição, tudo sem apelo programático.

E cadê o código que faz isso funcionar?

Bem a gente precisa escrver esse código, não tem jeito. Porém o bacana é que ele fica totalmente separado do texto da funcionalidade e dos cenários. O cucumber espera que você forneça para cada passo dos seus cenários, um bloco de código fonte, para que ele consiga executar os testes. Estes blocos de código são definidos dentro de um arquivo, que geralmente é chamado de “step.rb”, e precisa ficar dentro da pasta “features/step_definitions”.

Quando o cucumber interpreta o arquivo da feature e identifica  passos “indefinidos”, ele gera uma especie de template do passo, para que este possa ser implementado no arquivo de definicao de passos, o step.rb por exemplo. Um exemplo deste template seria:

Dado /^que a "([^\"]*)" possui "([^\"]*)" de "([^\"]*)" reais$/ do |arg1, arg2, arg3|
  pending # express the regexp above with the code you wish you had
end

Repare que tudo que foi informado entre “” foi considerado como parametro pelo cucumber, repare também que o template já abre um bloco de código onde ele nós fornece os argumentos. Entre este “do” e “end” é que colocamos a implementação do nosso passo. Veja agora um exemplo de implementação:

Dado /^que a "([^\"]*)" possui "([^\"]*)" de "([^\"]*)" reais$/ do |nome_da_conta, atributo, valor|
   conta = obter_conta_pelo_nome(nome_da_conta)
   atributo = change_spaces_for_underlines(atributo)
   conta.instance_variable_set( "@#{atributo}", valor.to_f )
end

O bacana é que os steps são reapoveitaveis, repare que o step do exemplo acima também é utilizado no cenário de saque com saldo insuficiente. No início vamos implementar muitos steps, mas depois vamos passar mais tempo escrevendo cenários e novas features do que implementando steps efetivamente. É possivel configurar o cucumber para fazer o output de seus testes em html. O interessante deste formato é que as funcionalidades e cenários ficam com “cara” de documentação “formal”.

Conclusão

É foi isso… A ideia aqui era focar no teste como forma de documentação. Apesar desta não ser uma ideia nova, vimos que o ruido programático dos testes pode atrapalhar. Terminamos vendo que o Cucumber resolve o problema do ruído na documentação executavel seprando a definicao das funcionalidades e cenários do código que faz tudo funcionar.

Abraços e até a próxima,

Sérgio Junior

Abaixo está a implementação completa da funcionalidade descrita neste post:

require 'lib/conta/conta'
 
Dado /^que a "([^\"]*)" possui "([^\"]*)" de "([^\"]*)" reais$/ do |nome_da_conta, atributo, valor|
   conta = obter_conta_pelo_nome(nome_da_conta)
   atributo = change_spaces_for_underlines(atributo)
   conta.instance_variable_set( "@#{atributo}", valor.to_f )
end
 
Dado /^que a "([^\"]*)" esta bloqueada$/ do |nome_conta|
   conta = obter_conta_pelo_nome(nome_conta)
   conta.bloqueada = true
end
 
Quando /^eu transferir "([^\"]*)" reais da "([^\"]*)" para "([^\"]*)"$/ do |valor, origem, destino|
  conta_origem = obter_conta_pelo_nome(origem)
  conta_destino = obter_conta_pelo_nome(destino)
  begin
    conta_origem.transfere :para => conta_destino, :valor => valor.to_f
  rescue Exception => e
    @erro = e
  end
end
 
Quando /^eu sacar "([^\"]*)" reais da "([^\"]*)"$/ do |valor, nome_conta|
  conta = obter_conta_pelo_nome(nome_conta)
  begin
    conta.saca valor.to_f
  rescue Exception => e
    @erro = e
  end
end
 
Quando /^eu depositar "([^\"]*)" reais na "([^\"]*)"$/ do |valor, nome_conta|
  conta = obter_conta_pelo_nome(nome_conta)
  begin
    conta.deposita valor.to_f
  rescue Exception => e
    @erro = e
  end
end
 
Entao /^"([^\"]*)" devera ter "([^\"]*)" de "([^\"]*)" reais$/ do |nome_da_conta, atributo, valor|
  conta = obter_conta_pelo_nome(nome_da_conta)
  atributo = change_spaces_for_underlines(atributo)
  conta.instance_variable_get( "@#{atributo}" ).should == valor.to_f
end  
 
Entao /^um erro de "([^\"]*)" deve ser exibido$/ do |mensagem|
  @erro.message.should == mensagem
end
 
private
  def obter_conta_pelo_nome(nome_da_conta)
    conta_simbolo = name_to_symbol(nome_da_conta)
    @contas ||= {}
    if @contas[conta_simbolo]
      conta = @contas[conta_simbolo]
    else
      conta = Conta.new
      @contas[conta_simbolo] = conta
    end
  end
 
  def change_spaces_for_underlines(name)
    name.gsub(/\s/,'_')
  end
 
  def name_to_symbol(name)
    change_spaces_for_underlines(name).to_sym
  end