rspec com doubles

Post on 26-Jun-2015

366 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

Apresentação dos métodos disponíveis no RSpec para métodos "doubles". Aplicações de mocks no contexto de testes

TRANSCRIPT

DesenvolvimentoBaseado em TestesRSpec - DoublesEduardo Mendesedumendes@gmail.com

@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 => "luiz@email.com") expect(subject.email).to eql("luiz@email.com") 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 "luiz@alternativo.com" end expect(subject.emailAlternativo).to eql("luiz@alternativo.com") end

it "possui email e partido" do subject.stub(:email => "luiz@email.com", :partido => "PUTZ") expect(subject.email).to eql("luiz@email.com") 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/

top related