rspec com doubles

94
Desenvolvimento Baseado em Testes RSpec - Doubles Eduardo Mendes [email protected]

Upload: eduardo-mendes-de-oliveira

Post on 26-Jun-2015

373 views

Category:

Technology


1 download

DESCRIPTION

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

TRANSCRIPT

Page 1: RSpec com doubles

DesenvolvimentoBaseado em TestesRSpec - DoublesEduardo [email protected]

Page 2: RSpec com doubles

@dudumendes

Introdução

Page 3: RSpec com doubles

@dudumendes

Introdução

O que se quer de um bom projeto

Princípios para alcançar

Situações que esclareçam

Page 4: RSpec com doubles

@dudumendes

RSpec::Mocks

Page 5: RSpec com doubles

@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

Page 6: RSpec com doubles

@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

Page 7: RSpec com doubles

@dudumendes

Stub com classes inexistentes

Page 8: RSpec com doubles

@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

Page 9: RSpec com doubles

@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

Page 10: RSpec com doubles

@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")

Page 11: RSpec com doubles

@dudumendes

Utilizando o subject

Page 12: RSpec com doubles

@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

Page 13: RSpec com doubles

@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

Page 14: RSpec com doubles

@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

Page 15: RSpec com doubles

@dudumendes

Exercício 1

Page 16: RSpec com doubles

@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

Page 17: RSpec com doubles

@dudumendes

método and_return

Page 18: RSpec com doubles

@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

Page 19: RSpec com doubles

describe UrnaEletronica do it "retorna um voto" do subject.stub(:apurar).and_return("Candidato 1") expect(subject.apurar).to eql("Candidato 1") endend

Page 20: RSpec com doubles

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

Page 21: RSpec com doubles

@dudumendes

método stub_chain

Page 22: RSpec com doubles

@dudumendes

testando a intimidademétodo stub_chain

O stub_chain

permite verificar o valor final retornado de uma chamada em cadeia de métodos

Page 23: RSpec com doubles

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

Page 24: RSpec com doubles

@dudumendes

método any_instance

Page 25: RSpec com doubles

@dudumendes

testando instâncias aleatóriasmétodo any_instance

O any_instance

cria expectativas sobre qualquer objeto de um classe

Page 26: RSpec com doubles

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

Page 27: RSpec com doubles

@dudumendes

Passando argumentos

Page 28: RSpec com doubles

@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

Page 29: RSpec com doubles

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

Page 30: RSpec com doubles

describe Candidato do it "inicializa com um numero" do Candidato.stub(:new).with(:numero => 99) Candidato.new :numero => 99 endend

Page 31: RSpec com doubles

it "inicializa com qualquer valor" do Candidato.stub(:new).with(any_args)

Candidato.new Candidato.new(:nome => "Valor", :idade => 19) end

Page 32: RSpec com doubles

it "inicializa com nome especifico" do Candidato.stub(:new).with(

hash_including(:nome => "Joao Luiz"))

Candidato.new(:nome => "Joao Luiz", :idade => 19) end

Page 33: RSpec com doubles

it "inicializa com nome especifico" do Candidato.stub(:new).with(

hash_not_including(:nome => "Joao Luiz"))

Candidato.new(:idade => 19) end

Page 34: RSpec com doubles

it "escreve um nome" do subject.stub(:nome=).with(/Joao/)

subject.nome= "Joao Luiz"end

Page 35: RSpec com doubles

it "o nome deve ser uma String" do subject.stub(:nome=).with(instance_of(String))

subject.nome= 3 end

Page 36: RSpec com doubles

it "deve ser ficha limpa" do subject.stub(:ficha_limpa=).with(boolean)

subject.ficha_limpa=true end

Page 37: RSpec com doubles

describe Candidato do it "inicializa com qualquer valor" do Candidato.stub(:new).with(anything)

Candidato.new(:nome => "Valor", :idade => 19) endend

Page 38: RSpec com doubles

@dudumendes

Retorno dependente do argumento

Page 39: RSpec com doubles

@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

Page 40: RSpec com doubles

@dudumendes

Stub de exceções

Page 41: RSpec com doubles

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

Page 42: RSpec com doubles

@dudumendes

Combinando classes

Page 43: RSpec com doubles

@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

Page 44: RSpec com doubles

@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

Page 45: RSpec com doubles

@dudumendes

Exercício 2

Page 46: RSpec com doubles

@dudumendes

Inscricao

A partir do spec da Inscricao, crie uma classe Inscricao que faça o teste passar

Page 47: RSpec com doubles

@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

Page 48: RSpec com doubles

@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

Page 49: RSpec com doubles

@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

Page 50: RSpec com doubles

@dudumendes

Message Expectations

Page 51: RSpec com doubles

@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á

Page 52: RSpec com doubles

@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

Page 53: RSpec com doubles

@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

Page 54: RSpec com doubles

@dudumendes

Stubs + Message Expectations

Page 55: RSpec com doubles

@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

Page 56: RSpec com doubles

@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

Page 57: RSpec com doubles

@dudumendes

Intenção no código

Sujeito

Inscrição

Colaborador primário

logger -- mock

Colaborador secundário

Candidato -- stub

Page 58: RSpec com doubles

@dudumendes

Exercício 3

Page 59: RSpec com doubles

@dudumendes

Inscricao

Adicione o exemplo do log no spec e o faça passar

Page 60: RSpec com doubles

@dudumendes

Counts

Page 61: RSpec com doubles

@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

Page 62: RSpec com doubles

@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

Page 63: RSpec com doubles

@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

Page 64: RSpec com doubles

@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

Page 65: RSpec com doubles

@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

Page 66: RSpec com doubles

@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

Page 67: RSpec com doubles

@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

Page 68: RSpec com doubles

@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

Page 69: RSpec com doubles

@dudumendes

Expectativas negativas

Page 70: RSpec com doubles

@dudumendes

Expectativas negativasshould_not_receive

should_not_receive

Utilizado quando não queremos que determinado sujeito receba uma mensagem durante o exemplo

Page 71: RSpec com doubles

@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

Page 72: RSpec com doubles

@dudumendes

Mensagem ordenadas

Page 73: RSpec com doubles

it "deve fazer campanha antes de votar" do subject.stub(:fazer_campanha).ordered subject.stub(:votar).ordered subject.fazer_campanha subject.votar

end

Page 74: RSpec com doubles

@dudumendes

Exercício 4

Page 75: RSpec com doubles

@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

Page 76: RSpec com doubles

@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

Page 77: RSpec com doubles

@dudumendes

Utilização de Mocks

Page 78: RSpec com doubles

@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

Page 79: RSpec com doubles

@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

Page 80: RSpec com doubles

@dudumendes

Sujeito

Interface para BD

Interface de rede

BD

Web

Page 81: RSpec com doubles

@dudumendes

Sujeito

StubInterface para BD

StubInterface de rede

Exemplo

Page 82: RSpec com doubles

@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

Page 83: RSpec com doubles

@dudumendes

Não determinismo local

DadoSujeito

Page 84: RSpec com doubles

@dudumendes

SujeitoStub doDadoExemplo 3,5,6,6,6,7,10

Page 85: RSpec com doubles

@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

Page 86: RSpec com doubles

@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

Page 87: RSpec com doubles

@dudumendes

Focos

Page 88: RSpec com doubles

@dudumendes

Foco no Papel

Mockar objetos permite a concentração no que importa

no que o objeto faz e não no que ele é

Page 89: RSpec com doubles

@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

Page 90: RSpec com doubles

@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

Page 91: RSpec com doubles

@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

Page 92: RSpec com doubles

@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

Page 93: RSpec com doubles

@dudumendes

Focar na interação ao invés do estado

Page 94: RSpec com doubles

@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/