Como Testar parte 2 - Mocks
Publicado por Marcos Tapajós há aproximadamente 1 mês.
Tem gente "chutando" Demeter.
Minha idéia era escrever um post dessa série por semana, mas infelizmente uma tendinite tem me atacado e está meio complicado ficar escrevendo muito. Por isso mesmo esse segundo artigo será bem compacto. 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
Nesse post a minha idéia é mostrar de forma bem simples como funciona um mock object no Rspec e no Test::Unit(usando o mocha).
Para exemplificar eu vou fazer uma crítica a uma construção que eu tenho visto muito em diversos projetos e que viola a Lei de Demeter(Principle of Least Knowledge). Ou como dizia, o meu amigo, Bernardo, "Tem gente chutando Demeter!".
Vamos a um exemplo dessa violação:
Supondo que você tenha um modelo Account que se relaciona ao modelo User.
class Account < ActiveRecord::Base
belongs_to :user
end
Freqüentemente eu vejo construções do tipo:
@account.user.name
@account.user.mail
@account.user.rg.number
@account.user.rg.state
@account.user.rg.city
O grande problema é que nesse tipo de construção você está "conhecendo" coisas demais e certamente vai pagar por isso num futuro breve, quando você precisar fazer um refactoring e tiver que mudar em vários lugares. Imagina se o User deixa de ter um "name" e passa a ter um "full_name".
Para resolver esse tipo de problema basta você concentrar esse "conhecimento" no seu modelo Account da seguinte forma:
class Account < ActiveRecord::Base
belongs_to :user
def user_name
user.name
end
end
OBS: Vou me concentrar apenas no problema com o nome e não vou me preocupar em validar se o relacionamento foi estabelecido.
Bem, finalizada a crítica a uma falha de design OO vamos ao objetivo desse post, mostrar como *EU* testaria esse problema.
Como eu falei no post anterior, não gosto muito de usar fixtures para testes unitários e por isso mesmo vou apelar aos mock objects. Se você não está muito familiarizado com mocks sugiro que pare por aqui e leia um pouco mais sobre isso. Uma referência rápida pode ser o wikipedia mas realmente sugiro que vá mais adiante.
Testando usando RSpec(usando o framework de mock padrão):
before(:each) do
@account = Account.new
@user_mock = mock_model(User)
@account.stub!(:user).and_return(@user_mock)
end
describe ".user_name" do
it "should delegate to user.name" do
@user_mock.should_receive(:name).and_return("Tapajós")
@account.user_name.should == "Tapajós"
end
end
Testando usando Test::Unit com mocha:
setup :create_model
def test_user_name
@user_mock.expects(:name).returns("Tapajós")
assert_equal "Tapajós", @account.user_name
end
private
def create_model
@account = Account.new
@user_mock = mock("User")
@account.stubs(:user).returns(@user_mock)
end
Explicando...
Em ambos os casos eu preciso que a minha account simule o relacionamento com User e para isso eu vou retornar um mock object. Isso é feito pelas linhas:
RSpec:
@account.stub!(:user).and_return(@user_mock)
Test::Unit:
@account.stubs(:user).returns(@user_mock)
Após o setup ou o before, temos um modelo @account onde o @account.user retorna um mock.
Feito isso eu preciso configurar o meu mock, isto é, informar que ele receberá uma mensagem name (chamada do método .name) e essa retornará o meu nome(sim, sou egocêntrico). Isso é feito pelos métodos "should_receive" e "expects" conforme as linhas abaixo:
RSpec:
@user_mock.should_receive(:name).and_return("Tapajós")
Test::Unit:
@user_mock.expects(:name).returns("Tapajós")
Depois que nossos mocks foram devidamente configurados podemos, finalmente, fazer nossa verificação do retorno, isto é, simplesmente chamar nossos métodos conferir o retorno. Pronto teste feito com sucesso, sem precisar recorrer a banco de dados nem configurar fixtures.
Nesse momento deve ter surgido uma dúvida: "Porque uma hora você usa stub! e outra um should_receive?"
A resposta é bem simples, o stub! não faz um verify no final enquanto a outra chamada sim. Na pratica isso significa:
Stub! ou stubs
Quando eu uso stub!(ou um Stubs) eu estou configurando o meu modelo account para responder pelo método user retornando o @mock_user porém não me interessa quebrar esse teste caso você não chame esse método.
Should_receive ou expects
Quando eu uso should_receive(ou um expects) eu estou configurando o meu mock user para responder pelo método name porém caso esse método não seja chamado eu devo quebrar meu teste, pois isso seria um comportamento indesejável.
Porque usar mocks?
Ao contrário do que muita gente pensa o uso de mocks não é um bicho de 7 cabeças, é bem simples. Na verdade testar é algo simples, desde que você tenha domínio do ferramental e os mocks são realmente úteis em diversos casos.
Imagina que o seu sistema precise fazer consultas a uma api publica do Yahoo e para isso você tenha criado uma classe de consultas. Você não vai querer(nem o Yahoo vai gostar) ir lá no servidor toda vez que você rodar os seus testes. Isso tornaria os seus testes lentos e impossível roda-los offline. Nesse caso você resolve seu problema "mockando" essa classe.





O que você achou? Coloque seus comentários e sugestões abaixo!
Acompanhe o RSS dessa página.
Comentários (7 até o momento)
Levy disse 8 dias depois:
Sylvestre Mergulhão disse 11 dias depois:
Tapajós disse 12 dias depois:
daniel lopes disse 12 dias depois:
Tapajós disse 14 dias depois:
Lucas de Castro disse 15 dias depois:
Daniel Lopes disse 18 dias depois: