Como Testar parte 1 - Models
Publicado por Marcos Tapajós há 24 dias.
Quando comecei a estudar Extreme Programming descobri que não é possível fazer nenhum software de qualidade sem uma excelente base de testes. Desde então tenho me dedicado muito ao estudo das mais diversas ferramentas e técnicas para elaborar bons de testes.
O assunto testes é bastante polêmico e não pretendo (nesse post) tentar convencer ninguém da importância deles. Se você não faz testes e/ou discorda de qualquer uma das minhas afirmações deixo algumas perguntas para você refletir.
1 - Quantos bugs fixes você fez esse ano?
2 - Quantos tickets abertos existem no seu bug tracker?
3 - Quantas vezes você fez um deploy de uma nova versão em uma sexta feira de tarde e saiu mais cedo do trabalho?
4 - Quantas vezes você "virou a noite" esse ano?
Acabei me tornando um evangelizador de testes porém nunca fiz nada muito prático para passar o conhecimento que eu adquiri para a comunidade. Só que agora vou me redimir dessa falha iniciando uma série de posts onde vou expor um problema e como EU testaria usando Test::Unit e Rspec. Não vou falar de Shoulda pois não gosto dele. :-)
A idéia de escrever essa série de posts sobre testes surgiu logo após a gravação do terceiro episódio do RailsBox e gostaria de agradecer ao Ozeias e ao Davis Cabral por terem me motivado.
"Back to the cold cow..."
O ActiveRecord simplifica muito nossos modelos porém tenho observado que em vários projetos os desenvolvedores deixam de testar corretamente os seus modelos usando a alegação que não vão testar alguma coisa que o Rails já testou. Esse é um argumento valido em alguns casos pois você está apenas delegando responsabilidades mas você sempre deve testar se a responsabilidade foi realmente delegada.
Um exemplo clássico são as validações. Teoricamente você não precisaria testar como elas são implementadas mas deve testar se elas realmente existem pois se alguém remove-las seus testes vão continuar passando mas sua aplicação estará quebrada e/ou permitindo inconsistências de banco de dados.
Nesse post vou mostrar como testar alguns comportamento do ActiveRecord usando como base o modelo User. Todos os códigos citados nesse post fazem parte de um projeto How Test que está disponível em: http://github.com/tapajos/how-test
class User < ActiveRecord::Base
validates_presence_of :name
validates_format_of :mail,
:with => /([-.\w^@]+@(?:[-\w]+.)+[A-Za-z]{2,4})+/i,
:on => :create,
:allow_nil => true
has_many :accounts
named_scope :actives, :conditions => ["active = ?", true]
end
Uma boa estratégia para orientar o desenvolvimento dos teste é elaborar algumas perguntas que darão origem aos seus cenários de testes.
Validação do nome
- Posso cadastrar um usuário sem nome? Não
Testando usando Test:Unit:
def test_if_check_presence_of_name
assert !@user.valid?, "Should be invalid"
assert_equal "can't be blank", @user.errors[:name]
end
Na primeira linha desse teste o assert recebe um segundo parâmetro que por ser opcional não é muito comentado mas merece uma atenção especial. Esse argumento nada mais é do que a mensagem que será exibida quando o teste quebrar. Quando você omite esse parâmetro o teste quebra exibindo a mensagem 'false is not true' que não ajuda muito a entender o que está acontecendo.
Testando usando RSpec:
it "should reject if name is not given" do
@user.should have(1).error_on(:name)
@user.errors[:name].should == "can't be blank"
end
Validação do e-mail.
- Posso criar um registro com um e-mail inválido? Não
- Posso atualizar um regitro com um e-mail inválido? Sim
- Posso criar um registro com um e-mail em branco? Sim
Testando usando Test:Unit:
VALIDS_MAIL = %w(foo@bar.com foo@bar.com.br foo@globo.com foo@i_hate_the_microsoft.com foo@i_love_my_mac.com)
INVALIDS_MAIL = %w(foobar.com foo@bar i_hate_the_microsoft.com i_love_my_mac.com)
def test_if_reject_invalid_format_os_mail_on_create
INVALIDS_MAIL.each do |mail|
@user.mail = mail
assert !@user.valid?, "Should be invalid when mail is #{mail}"
assert_equal "is invalid", @user.errors[:mail]
end
end
def test_if_not_reject_when_mail_is_nil
@user.name = "Tapajós"
assert @user.valid?, "Should be valid"
end
def test_if_not_check_format_of_mail_on_update
@user.name = "Tapajós"
assert @user.save, "Should save"
@user.mail = "an invalid mail"
assert @user.valid?, "Should be valid"
end
def test_if_accept_a_valid_mail
VALIDS_MAIL.each do |mail|
@user.name = "Tapajós"
@user.mail = mail
assert @user.valid?, "Should be valid when mail is #{mail}"
end
end
Testando usando RSpec:
INVALIDS_MAIL.each do |mail|
it "should reject because #{mail} is an invalid mail" do
@user.mail = mail
@user.should have(1).error_on(:mail)
@user.errors[:mail].should == "is invalid"
end
end
VALIDS_MAIL.each do |mail|
it "should be valid when mail is #{mail}" do
@user.mail = mail
@user.should_not have(1).error_on(:mail)
end
end
it "should not reject if mail is not given" do
@user.name = "Tapajós"
@user.should be_valid
end
it "should not check mail format on update" do
@user.name = "Tapajós"
@user.save.should be_true
@user.mail = "an invalid mail"
@user.should be_valid
end
Testando o relacionamento com Account.
- Um usuário pode ter mais de uma conta? Sim
Testando usando Test::Unit:
def test_has_many_accounts
association = User.reflect_on_association(:accounts)
assert association, "Association with account is not found"
assert_equal :has_many, association.macro
end
Testando usando RSpec:
it "should has many accounts" do
association = User.reflect_on_association(:accounts)
association.should_not be_nil
association.macro.should == :has_many
end
Testando o User.actives
- Posso listar usuários inativos? Não
Testando usando Test::Unit:
def test_if_actives_use_the_correct_conditions
assert_equal({:conditions=>["active = ?", true]}, User.actives.proxy_options)
end
Testando usando RSpec:
it "should find for all users that status of active is true" do
User.actives.proxy_options.should == {:conditions=>["active = ?", true]}
end
Para esse post ficar mais simples e curto não me preocupei em validar se o tamanho máximo dos campos está coerente com o tamanho máximo permitido pelo tipo no banco de dados. Essa é uma validação EXTREMAMENTE importante que não deve ser esquecida!
No próximo post dessa série falarei um pouco sobre a Lei de Demeter, como respeita-la e testar alguns métodos usando Mock Objects.
O que você achou desse artigo? O que você gostaria de saber sobre testes de ActiveRecord que eu não falei aqui?
Aguardo o feedback de vocês.





