RSpec com doubles
-
Upload
eduardo-mendes-de-oliveira -
Category
Technology
-
view
366 -
download
1
description
Transcript of RSpec com doubles
DesenvolvimentoBaseado em TestesRSpec - DoublesEduardo [email protected]
@dudumendes
Introdução
@dudumendes
Introdução
O que se quer de um bom projeto
Princípios para alcançar
Situações que esclareçam
@dudumendes
RSpec::Mocks
@dudumendes
Criando doublesmétodo double
algum_double = double(“um_double”)
algum_stub = stub(“um_stub”)
algum_mock = mock(“um_mock”)
Argumento string é opcional, mas recomendado
pode ser utilizado um símbolo
Utilizado na mensagens de falha
Geram instâncias de RSpec::Mocks::Mock
@dudumendes
Métodos Stubs
Método em que se pode programar uma resposta pré-definida de um objeto,
que será retornada durante a execução de exemplo
utiliza-se quando não se tem expectativas sobre a execução
@dudumendes
Stub com classes inexistentes
@dudumendes
Classe inexistente
describe "classe Candidato" do it "retorna nome e partido" do candidato = mock(:candidato) candidato.stub(:nome => "Luiz Augusto", :partido => "PRAONDEEH")
expect(candidato.nome).to eql("Luiz Augusto") expect(candidato.partido).to eql("PRAONDEEH") endend
Identificador do mock
métodos e retornos
@dudumendes
Classe inexistente / atalho
describe "classe Candidato" do it "retorna nome e partido" do candidato = mock(:candidato,
:nome => "Luiz Augusto", :partido => "PRAONDEEH")
expect(candidato.nome).to eql("Luiz Augusto") expect(candidato.partido).to eql("PRAONDEEH") endend
Identificador do mock
métodos e retornos
@dudumendes
candidato = mock(:candidato)candidato.stub(:nome => "Luiz Augusto", :email => "PRAONDEEH")
candidato = mock(:candidato, :nome => "Luiz Augusto", :partido => "PRAONDEEH")
candidato = mock(:candidato)candidato.stub(:nome).and_return("Luiz Augusto")candidato.stub(:email).and_return("PRAONDEEH")
@dudumendes
Utilizando o subject
@dudumendes
o método subject
Subject
O subject de um exemplo é o objeto que está sendo descrito, exercitado
Se o subject é uma classe chamada Usuario
uma instância de Usuario é fornecida automaticamente pelo método subject
subjects são instanciados nos blocos before
@dudumendes
describe Professor do it "eh uma instancia de Professor" do expect(subject).to be_a(Professor) end it "nao deve ser um aluno" do expect(subject).not_to be_an(Aluno) end it "nao possui nome definido" do expect(subject.nome).to be_nil endend
Professor eh uma instancia de Professor nao deve ser um aluno nao possui nome definido
@dudumendesdescribe Candidato do it "possui email" do subject.stub(:email => "[email protected]") expect(subject.email).to eql("[email protected]") end it "pode ter partido nulo" do subject.stub(:partido) expect(subject.partido).to be_nil end
it "possui email alternativo" do subject.stub(:emailAlternativo) do "[email protected]" end expect(subject.emailAlternativo).to eql("[email protected]") end
it "possui email e partido" do subject.stub(:email => "[email protected]", :partido => "PUTZ") expect(subject.email).to eql("[email protected]") expect(subject.partido).to eql("PUTZ") endend
@dudumendes
Exercício 1
@dudumendes
Funcionario
Crie specs com mocks para a classe funcionario e exercite expectativas em valores pré-configurados
Faça os testes falharem e passarem para comparar os resultados
Crie 02 versões
um spec com uma classe que não existe
e outro com utilizando o subject
@dudumendes
método and_return
@dudumendes
retornando vários valoresmétodo and_return
O and_return
é uma alternativa para definição do valor a ser retornado
possibilita a passagem de vários valores
describe UrnaEletronica do it "retorna um voto" do subject.stub(:apurar).and_return("Candidato 1") expect(subject.apurar).to eql("Candidato 1") endend
describe UrnaEletronica do it "retorna votos em sequencia" do subject.stub(:apurar).and_return("C1", "C2", "C3") expect(subject.apurar).to eql("C1") expect(subject.apurar).to eql("C2") expect(subject.apurar).to eql("C3") endend
@dudumendes
método stub_chain
@dudumendes
testando a intimidademétodo stub_chain
O stub_chain
permite verificar o valor final retornado de uma chamada em cadeia de métodos
describe "classe Estacao" do it "retorna a previsao de temperatura maxima" do subject.stub_chain(:termometro, :maxima => 32) expect(subject.termometro.maxima).to eql(32) end it "retorna a previsao de temperatura minima" do subject.stub_chain(:termometro, :minima => 32) expect(subject.termometro.minima).to eql(32) endend
@dudumendes
método any_instance
@dudumendes
testando instâncias aleatóriasmétodo any_instance
O any_instance
cria expectativas sobre qualquer objeto de um classe
describe Eleitor do it "deve votar" do Eleitor.any_instance.stub(:votar => true) eleitor = Eleitor.new expect(eleitor.votar).to be_true novo_eleitor = Eleitor.new expect(novo_eleitor.votar).to be_true endend
@dudumendes
Passando argumentos
@dudumendes
passando argumentosmétodo with
O with
passa os parâmetros que devem ser passados a um método de stub
valor
hash
anything, any_args, hash_including(), hash_not_including(), instance_of
describe Eleitor do it "deve votar" do Eleitor.any_instance.stub(:votar).with(:voto).and_return(true) eleitor = Eleitor.new expect(eleitor.votar(:voto)).to be_true novo_eleitor = Eleitor.new expect(novo_eleitor.votar(:voto)).to be_true endend
describe Candidato do it "inicializa com um numero" do Candidato.stub(:new).with(:numero => 99) Candidato.new :numero => 99 endend
it "inicializa com qualquer valor" do Candidato.stub(:new).with(any_args)
Candidato.new Candidato.new(:nome => "Valor", :idade => 19) end
it "inicializa com nome especifico" do Candidato.stub(:new).with(
hash_including(:nome => "Joao Luiz"))
Candidato.new(:nome => "Joao Luiz", :idade => 19) end
it "inicializa com nome especifico" do Candidato.stub(:new).with(
hash_not_including(:nome => "Joao Luiz"))
Candidato.new(:idade => 19) end
it "escreve um nome" do subject.stub(:nome=).with(/Joao/)
subject.nome= "Joao Luiz"end
it "o nome deve ser uma String" do subject.stub(:nome=).with(instance_of(String))
subject.nome= 3 end
it "deve ser ficha limpa" do subject.stub(:ficha_limpa=).with(boolean)
subject.ficha_limpa=true end
describe Candidato do it "inicializa com qualquer valor" do Candidato.stub(:new).with(anything)
Candidato.new(:nome => "Valor", :idade => 19) endend
@dudumendes
Retorno dependente do argumento
@dudumendesdescribe "Bar" do it "so pode vender para maior de 18" do cliente = double(:cliente) cliente.stub(:beber) do |idade| if idade >= 18 "OK" else "ERROR" end end expect(cliente.beber(20)).to eql "OK" expect(cliente.beber(10)).to eql "ERROR" endend
@dudumendes
Stub de exceções
describe Eleitor do it "raises" do subject.stub(:idade).and_raise("Nao implementado") expect { subject.idade }.to raise_error("Nao implementado") end it "throws" do subject.stub(:votar).and_throw(:nao_comparecimento) expect { subject.votar }.to throw_symbol(:nao_comparecimento) endend
@dudumendes
Combinando classes
@dudumendes
describe Inscricao do it "utiliza o nome do candidato no cabecalho" do
candidato = double("candidato") candidato.stub(:nome).and_return("Luiz")
inscricao = Inscricao.new(candidato) expect(inscricao.gerar).to eql "Inscricao de Luiz"
endend
@dudumendes
describe Inscricao do it "utiliza o nome do candidato no cabecalho" do candidato = double("candidato") candidato.stub(:nome).and_return("Luiz")
inscricao = Inscricao.new(candidato) expect(inscricao.gerar).to eql "Inscricao de Luiz" endend
Inscricao: sujeitoCandidato: não é o foco do exemplo, coloborador imediato
Teste double para atuar como um candidato
@dudumendes
Exercício 2
@dudumendes
Inscricao
A partir do spec da Inscricao, crie uma classe Inscricao que faça o teste passar
@dudumendes
describe Inscricao do it "utiliza o nome do candidato no cabecalho" do candidato = double("candidato") candidato.stub(:nome).and_return("Luiz")
inscricao = Inscricao.new(candidato) expect(inscricao.gerar).to eql "Inscricao de Luiz" endend
@dudumendes
EstratégiasTriangulação
Criar um outro exemplo utilizando um valor diferente que força a generalização do método
Verificacar duplicação
Verifica-se que "Inscricao de Luiz" é uma duplicação
aparece no spec e no método
consequência: remoção
@dudumendes
Estratégias
Triangulação
Exige 02 exemplos para que o sujeito tenha o comportamento esperado
Verificacar duplicação
Pode legar valores “hard-coded” à implementação
@dudumendes
Message Expectations
@dudumendes
Expectativas de mensagensmétodo should_receive
should_receive
caso a mensagem programada nunca seja chamada
o método lançará um erro
o teste falhará
@dudumendes
describe Inscricao do it "utiliza o nome do candidato no cabecalho" do candidato = double("candidato") candidato.should_receive(:nome).and_return("Luiz")
inscricao = Inscricao.new(candidato)
expect(inscricao.gerar).to eql "Inscricao de Luiz" endend
@dudumendes
class Inscricao def initialize(candidato) @candidato = candidato end def gerar "Inscricao de Luiz" endend
Failure/Error: candidato.should_receive(:nome).and_return("Luiz") (Double "candidato").nome(any args) expected: 1 time received: 0 times# ./inscricao_spec_2.rb:14:in `block (2 levels) in <top (required)>'
Finished in 0.00216 seconds1 example, 1 failure
@dudumendes
Stubs + Message Expectations
@dudumendes
Stubs + Message Expectations
O sentido de existir métodos que retornam o mesmo objeto
dar mais semântica ao teste
identificar sujeito e colaboradores
intenção incorporada no código
@dudumendes
it "cria um log quando o gerar eh chamado" do candidato = stub("candidato") candidato.stub(:nome).and_return("Luiz")
logger = mock("logger") inscricao = Inscricao.new(candidato, logger) logger.should_receive(:log).with("Inscricao de Luiz") inscricao.gerar end
@dudumendes
Intenção no código
Sujeito
Inscrição
Colaborador primário
logger -- mock
Colaborador secundário
Candidato -- stub
@dudumendes
Exercício 3
@dudumendes
Inscricao
Adicione o exemplo do log no spec e o faça passar
@dudumendes
Counts
@dudumendes
Counts
should_receive
A expectativa default gerada por uma chamada a should_receive é que a mensagem seja chamada apenas 01 única vez
é possível configurar o número de vezes através de métodos como exactly(), at_least(), at_most(), once, twice, combinados com o método times
@dudumendes
describe "classe Aluno" do it "chama new" do aluno = double(:aluno) aluno.should_receive(:nome).and_return("Jessica") expect(aluno.nome).to eql "Jessica" endend
@dudumendes
describe "classe Aluno" do it "chama new" do aluno = double(:aluno) aluno.should_receive(:nome).and_return("Jessica") expect(aluno.nome).to eql "Jessica" expect(aluno.nome).to eql "Jessica" endend
@dudumendes
describe "classe Aluno" do it "chama new" do aluno = double(:aluno) aluno.should_receive(:nome)
.and_return("Jessica").exactly(1).times expect(aluno.nome).to eql "Jessica" endend
exactly().times
@dudumendes
describe "Rede" do it "deve ser solicitadas no maximo 5 conexoes" do rede = double(:rede) rede.should_receive(:open_connection).at_most(4).times rede.open_connection rede.open_connection rede.open_connection rede.open_connection endend
at_most().times
@dudumendes
describe "Rede" do it "deve ser solicitadas no maximo 5 conexoes" do rede = double(:rede) rede.should_receive(:open_connection).at_least(2).times rede.open_connection rede.open_connection rede.open_connection rede.open_connection endend
at_least().times
@dudumendes
once, twicedescribe "Conta" do it "deve gerar extrato 01 vez" do conta = double(:conta) conta.should_receive(:gerar_extrato).once conta.gerar_extrato end
it "deve checar valor 02 vezes" do conta = double(:conta) conta.should_receive(:checar_valor).twice
conta.checar_valor conta.checar_valor endend
@dudumendes
it "nao deve abrir conexao apos ping falso" do @rede.stub(:ping).and_return(false)
@rede.should_receive(:open_connection).exactly(0).times @rede.open_connection if @rede.ping end
@dudumendes
Expectativas negativas
@dudumendes
Expectativas negativasshould_not_receive
should_not_receive
Utilizado quando não queremos que determinado sujeito receba uma mensagem durante o exemplo
@dudumendes
it "nao deve abrir conexao apos ping falso" do @rede.stub(:ping).and_return(false)
@rede.should_not_receive(:open_connection) @rede.open_connection if @rede.ping end
it "nao deve abrir conexao apos ping falso" do @rede.stub(:ping).and_return(false)
@rede.should_receive(:open_connection).never @rede.open_connection if @rede.ping end
@dudumendes
Mensagem ordenadas
it "deve fazer campanha antes de votar" do subject.stub(:fazer_campanha).ordered subject.stub(:votar).ordered subject.fazer_campanha subject.votar
end
@dudumendes
Exercício 4
@dudumendes
TransferenciaContexto
Testar a transferencia de valores entre 02 contas
A transferência é realizada por um objeto chamado Transferencia
O objeto guarda as 02 contas e executa uma transferência de valores entre elas
O sujeito a se testar é o objeto Transferencia
As contas ainda não estão implementadas
@dudumendes
TransferenciaExemplos
o objeto Transferencia deve ser criado com 01 conta de origem, 01 conta de destino e um valor
ao se executar a transferência, a conta de origem deve receber a mensagem transferir
o 1.º argumento deve ser a conta de destino
o 2.º argumento deve uma instância de Fixnum
o 2.º argumento deve ter o valor
deve ser lançado um erro com a mensagem “Saldo insuficiente”, caso o saldo da conta de origem seja menor que o valor solicitado
o saldo deve ser conferido antes de transferir
@dudumendes
Utilização de Mocks
@dudumendes
Isolar dependências
Código fracamente acoplado
possui dependências
Se os objetos são fáceis e “baratos” de construir
não utilize mocks ou stubs
@dudumendes
Isolar dependênciasDependências problemáticas
configuração e construção cara
funcionamento lento
dependência de sistemas externos
rede, servidores, sistema de arquivos
Mock para isolar os exemplos das dependências e incrementar potenciais pontos de falha
@dudumendes
Sujeito
Interface para BD
Interface de rede
BD
Web
@dudumendes
Sujeito
StubInterface para BD
StubInterface de rede
Exemplo
@dudumendes
Isolação de comportamentos não determinísticos
Dependência de sistemas externos
pode ser fonte de não determinismo
arquivos corrompidos, falhas de disco, time out de rede
MOCK e obtenha um ambiente controlado
@dudumendes
Não determinismo local
DadoSujeito
@dudumendes
SujeitoStub doDadoExemplo 3,5,6,6,6,7,10
@dudumendes
Progresso sem implementações
Às vezes, dependemos de comportamentos de objetos que outros times não implementaram ainda
As interfaces podem já ter sido projetadas
Oportunidade para explorar dependências e possibilidades de interfaces alternativas
@dudumendes
Descobrimento de interface
Ao exercitar a implementação de um objeto
pode-se descobrir que ele necessita de comportamento de um outro que ainda não existe
método não pensado na fase de projeto
até mesmo objeto
@dudumendes
Focos
@dudumendes
Foco no Papel
Mockar objetos permite a concentração no que importa
no que o objeto faz e não no que ele é
@dudumendes
it "cria um log quando o gerar eh chamado" do candidato = stub("candidato") candidato.stub(:nome).and_return("Luiz")
logger = mock("logger") inscricao = Inscricao.new(candidato, logger) logger.should_receive(:log).with("Inscricao de Luiz") inscricao.gerar end
@dudumendes
Focar na interação ao invés do estado
Sistemas orientados a objetos dizem respeito à interfaces e interações
O estado não faz parte do comportamento observável
Exemplos serão menos frágeis
se evitar o foco no estado
@dudumendes
describe Inscricao do it "utiliza o nome do candidato no cabecalho" do candidato = stub("candidato", :nome => "Luiz") inscricao = Inscricao.new(candidato)
expect(inscricao.gerar).to eql "Inscricao de Luiz" endend
@dudumendes
describe Inscricao do it "utiliza o nome do candidato no cabecalho" do candidato = double("candidato") candidato.should_receive(:nome).and_return("Luiz")
inscricao = Inscricao.new(candidato)
expect(inscricao.gerar).to eql "Inscricao de Luiz" endend
@dudumendes
Focar na interação ao invés do estado
@dudumendes
Bibliografia
FOWLER, Martin. “Mocks aren’t Stubs”.
FREEMAN, Steve; PRYCE, Nat. Growing Object-Oriented Software, Guiaded by Tests. Addison-Wesley.
MESZAROS, Gerard. xUnit Test Patterns: RefactoringTest Code. Addison-Wesley: 2007
MESZAROS, Gerard. xUnitTest Patterns.com. http://xunitpatterns.com/