Escrevi um pouco apressado, estarei viajando daqui a pouco e como já tinha uma parte no ‘draft’, resolvi publicar logo. :)
O código apresentado nos exemplos está utilizando os métodos da versão do RSpec 0.9, que foi lançado ontem na lista de usuários e o site do projeto ainda apresenta a versão 0.8.2(até o momento), porém nada complicado de “converter”.
Então, aqui vamos. :)
Pequena Introdução
Muito, e há muito tempo, tem se falado de Desenvolvimento orientado a Testes, ou TDD(Test-Driven Development). Metodologias, como a Extreme Programming, que fazem sua utilização, além de frameworks e ferramentas para as mais diversas linguagens. “Mas será que estamos fazendo corretamente? TDD é realmente sobre testes?”. Foram alguns dos questionamentos que levaram a Dave Astels e Dan North a criarem outra alternativa: BDD, Behaviour Driven Development, ou Desenvolvimento orientado a Comportamento. Notaram que experientes praticantes de TDD chegavam a um ponto que percebiam que não estavam escrevendo testes e sim definindo comportamentos. Tendo isso em mente, desenvolveram frameworks que permitissem um melhor foco nessas definições: JBehave e RSpec(tema deste post).
Instalando
gem install rspec
Possui algumas dependências como o rcov e o rake, acesse a documentação para outras.
Estrutura básica
#no "describe" descrevemos o que estamos especificando no momento
describe Carteira do
#este método é chamado antes de cada "it", pode ser utilizado para indicar um estado dos objetos envolvidos e mocks.
setup do
...
end
#nos métodos "it" é onde realmente trabalhamos
it "deve ter um método para entrada $" do
...
end
it "não deve estar vazia" {
...
}
end
#podemos definir mais de um describe por arquivo.
describe Carteira, " (validações)" do #este describe resultaria em: "Carteira (validações)"
...
end
Should e Expectations
O RSpec adiciona os métodos should(‘devia’) e should_not(‘não devia’) à Object, tornando disponível para qualquer objeto de sua aplicação (durantes os testes :) ):
#exemplos
carteira.should_not ...
post.should_not ...
post.save!.should ...
E também define um conjunto de Expectations(‘expectativas’) para que possamos definir os comportamentos esperados.
#exemplos
be_...(...)
have(...).(...)
raise_error(...)
‘Juntando’:
carteira.should_not be_a_kind_of(Carro)
#carteira não devia ser do tipo Carro
post.should_not have(3).comentarios
#post não deve ter 3 comentários
post.save!.should raise_error
#post.save! devia causar erro
A leitura flui melhor do que ‘assert aquilo’, não? :) Veja outros Expectations.
Exemplo
Como exemplo vamos definir o comportamento de Carteira. Primeiro criamos o “esqueleto”:
#arquivo carteira_spec.rb
require 'carteira' #vamos criar mais abaixo
describe Carteira do
setup do
@carteira = Carteira.new
end
end
Agora vamos para o primeiro comportamento:
- o valor inicial de dinheiro deve ser 0. :|
it "o valor inicial de dinheiro deve ser 0 :|" do
@carteira.dinheiro.should == 0
@carteira.dinheiro.should eql?(0) #alternativa 1
@carteira.dinheiro.should be_zero #alternativa 2
end
Três formas de (d)escrever. Na última o “be_zero” na verdade está utilizando o método “zero?” de dinheiro(Fixnum), servindo para qualquer ‘predicate’ (método terminado em ’?’). Ex: be_include (Array:include?), be_empty(String:empty?).
Mais dois comportamentos:- o método ganho(x) deve incrementar o dinheiro em x. :)
- o método gasto(x) deve decrementar o dinheiro em x, lançar erro SemGranaError caso o valor for se tornar negativo. :(
it "o método ganho(x) deve incrementar o dinheiro em x. :)" do
@carteira.ganho(3)
@carteira.dinheiro.should == 3 #outra forma! =]
end
it "o método gasto(x) deve decrementar o dinheiro em x, lançar erro SemGranaError caso o valor for se tornar negativo. :(" do
lambda{ @carteira.gasto(3) }.should raise_error(SemGranaError)
@carteira.ganho(3)
@carteira.gasto(3).should == 0
end
Fácil, não? Agora vamos criar nossa Carteira, para podermos executar os testes(?):
#arquivo carteira.rb
class Carteira
attr_accessor :dinheiro
def initialize
end
def ganho(dinheiro)
end
def gasto(dinheiro)
end
end
Para executar: spec carteira_spec.rb. Como saída teremos:
FFF
1)
NoMethodError in 'Carteira o valor inicial de dinheiro deve ser 0 :|'
undefined method `dinheiro' for #<Carteira:0xb7c51270>
./carteira_spec.rb:10:
2)
NoMethodError in 'Carteira o método ganho(x) deve incrementar o dinheiro em x. :)'
undefined method `dinheiro' for #<Carteira:0xb7c3b484>
./carteira_spec.rb:17:
3)
NameError in 'Carteira o método gasto(x) deve decrementar o dinheiro em x, lançar erro SemGranaError caso o valor for se tornar negativo. :('
uninitialized constant SemGranaError
./carteira_spec.rb:21:
Finished in 0.009035 seconds
3 examples, 3 failures
Cada ‘F’ no início representa uma falha e cada tópico descreve uma falha. É interessante que tudo falhe na primeira vez, para sabermos que funcionou depois que implementamos (oras!).
Implementando a primeira especificação: def initialize
@dinheiro = 0
end
Executando(spec carteira_spec.rb), temos:
.FF
1)
'Carteira o método ganho(x) deve incrementar o dinheiro em x. :)' FAILED
expected 3, got 0 (using ==)
./carteira_spec.rb:17:
2)
NameError in 'Carteira o método gasto(x) deve decrementar o dinheiro em x, lançar erro SemGranaError caso o valor for se tornar negativo. :('
uninitialized constant SemGranaError
./carteira_spec.rb:21:
Finished in 0.008884 seconds
3 examples, 2 failures
“E cadê o que eu fiz?”. Virou um ’.’, aquele pontinho quer dizer que funcionou.
Finalizando implementando o resto:#arquivo carteira.rb
class Carteira
attr_accessor :dinheiro
def initialize
@dinheiro = 0
end
def ganho(dinheiro)
@dinheiro = @dinheiro + dinheiro
end
def gasto(dinheiro)
raise SemGranaError if (@dinheiro - dinheiro) < 0
@dinheiro = @dinheiro - dinheiro
end
end
class SemGranaError < StandardError
end
Saída:
...
Finished in 0.007725 seconds
3 examples, 0 failures
Experimente outras opções de formatação, para olhar as opções execute: spec – -help
Fim!
Gostou? Se sim, no próximo projeto utilize o RSpec, ou alguma ferramenta BDD ou TDD, seu aplicativo ficará saudável, não tem mais desculpa. (Não preparei nada para a alternativa ‘não’ :D ).
Já estou preparando posts sobre mock e rspec_on_rails.
Comporte-se e até a próxima.