Blog da Improve It

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

  1. 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.

  1. Posso criar um registro com um e-mail inválido? Não
  2. Posso atualizar um regitro com um e-mail inválido? Sim
  3. 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.

  1. 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

  1. 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.

Tags , , , , , ,  | 43 comentários

Beonthe.net: cem pessoas na fila de espera

Publicado por Vinicius Manhães Teles há aproximadamente 1 mês.

Há alguns meses nós montamos uma fila de espera para o Beonthe.net e a quantidade de pessoas vinha crescendo gradualmente. Mas, de repente, alguma coisa aconteceu. Durante o mês de setembro, que acabou de terminar, a fila simplesmente dobrou de tamanho. No início do mês havia pouco menos de 50 pessoas, enquanto ao final já tínhamos pouco mais de 100. Hoje temos 107 pessoas registradas, para ser exato.

Conversei por telefone com a maioria dessas pessoas e o feedback não poderia ser melhor. Com isso, estamos cada vez mais animados com o que está saindo, embora levemente apreensivos com a quantidade de gente que teremos que atender desde o início.

Para melhorar as coisas um pouco mais, a última edição da Revista Fhox trouxe uma matéria da minha esposa, Patricia Figueira, falando sobre a importância do site para os fotógrafos. O título é sugestivo: Não esqueça o site; é o cartão-de-visita. Vejam abaixo:

Matéria na Revista Fhox: Não esqueça o site; é o cartão-de-visita.

Tags ,  | 2 comentários