rspec best friends @ tdc florianópolis 2014

Post on 19-Jan-2015

635 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

Nesta palestra veremos: - Boas práticas ao escrever testes utilizando o RSpec - Como escrever testes que acessam rede utilizando o VCR e o WebMock - Apresentando o factory_girl, comparando com as fixtures. E diversas dicas do factory_girl - Testes que dependem de data utilizando o timecop - Coverage de testes com o Simplecov e se devemos ou não atingir os 100% de cobertura de testes - Evitando repetições durante os testes utilizando de matchers

TRANSCRIPT

RSpec Best Friends

Mauro quem...

RSpec Best Friends

RSpec Best Friends

maurogeorge.com.br

RSpec

sintaxe de expectativaRSpec

spec/models/pokemon_spec.rb

it 'exibe o nome e o id nacional' do pokemon = Pokemon.new(nome: 'Charizard', id_nacional: 6) pokemon.nome_completo.should eq('Charizard - 6')end

spec/models/pokemon_spec.rb

it 'exibe o nome e o id nacional' do pokemon = Pokemon.new(nome: 'Charizard', id_nacional: 6) expect(pokemon.nome_completo).to eq('Charizard - 6')end

spec/models/pokemon_spec.rb

it { expect(subject).to be_a(ActiveRecord::Base) }

spec/models/pokemon_spec.rb

it { should be_a(ActiveRecord::Base) }

spec/models/pokemon_spec.rb

it { is_expected.to be_a(ActiveRecord::Base) }

Somente RSpec 3

spec/spec_helper.rb

RSpec.configure do |config| # ... config.expect_with :rspec do |c| c.syntax = :expect endend

descrevendo melhor os testesRSpec

spec/models/pokemon_spec.rb

it 'exibe o nome e o id nacional' do pokemon = Pokemon.new(nome: 'Charizard', id_nacional: 6) expect(pokemon.nome_completo).to eq('Charizard - 6')end

spec/models/pokemon_spec.rb

describe '#nome_completo' do it 'exibe o nome e o id nacional' do pokemon = Pokemon.new(nome: 'Charizard', id_nacional: 6) expect(pokemon.nome_completo).to eq('Charizard - 6') endend

não teste apenas o happy pathRSpec

spec/models/pokemon_spec.rb

describe '#nome_completo' do it 'exibe o nome e o id nacional' do pokemon = Pokemon.new(nome: 'Charizard', id_nacional: 6) expect(pokemon.nome_completo).to eq('Charizard - 6') endend

spec/models/pokemon_spec.rb

describe '#nome_completo' do it 'exibe o nome e o id nacional quando possui os valores' do pokemon = Pokemon.new(nome: 'Charizard', id_nacional: 6) expect(pokemon.nome_completo).to eq('Charizard - 6') end

it 'é nil quando não possui o nome e o id nacional' do pokemon = Pokemon.new expect(pokemon.nome_completo).to be_nil endend

contextos para a melhor descriçãoRSpec

spec/models/pokemon_spec.rb

describe '#nome_completo' do context 'quando possui nome e o id nacional' do it 'exibe o nome e o id nacional' do # ... end end

context 'quando não possui o nome e o id nacional' do it 'é nil' do # ... end endend

de!nindo o sujeitoRSpec

spec/models/pokemon_spec.rb

context 'quando possui nome e o id nacional' do before do @pokemon = Pokemon.new(nome: 'Charizard', id_nacional: 6) end

it 'exibe o nome e o id nacional' do expect(@pokemon.nome_completo).to eq('Charizard - 6') endend

spec/models/pokemon_spec.rb

context 'quando possui nome e o id nacional' do let(:pokemon) do Pokemon.new(nome: 'Charizard', id_nacional: 6) end

it 'exibe o nome e o id nacional' do expect(pokemon.nome_completo).to eq('Charizard - 6') endend

spec/models/pokemon_spec.rb

context 'quando possui nome e o id nacional' do subject do Pokemon.new(nome: 'Charizard', id_nacional: 6) end

it 'exibe o nome e o id nacional' do expect(subject.nome_completo).to eq('Charizard - 6') endend

utilize sempre os matchersRSpec

spec/models/pokemon_spec.rb

it 'é nil' do expect(subject.nome_completo).to eq(nil)end

spec/models/pokemon_spec.rb

it 'é nil' do expect(subject.nome_completo).to be_nilend

não use shouldRSpec

spec/models/pokemon_spec.rb

it 'should have the name and the national_id' do expect(pokemon.full_name).to eq('Charizard - 6')end

spec/models/pokemon_spec.rb

it 'does have the name and the national_id' do expect(pokemon.full_name).to eq('Charizard - 6')end

ordem aleatória nos testesRSpec

spec/spec_helper.rb

RSpec.configure do |config| # ... config.order = "random"end

coding styleRSpec

https://github.com/mongoid/mongoid

coding styleRSpec

https://github.com/mongoid/mongoidhttps://github.com/bbatsov/ruby-style-guide

coding styleRSpec

Testes que acessam rede

introduçãoTestes que acessam rede

Testes lentos

introduçãoTestes que acessam rede

Testes lentosTestes quebradiços

introduçãoTestes que acessam rede

Testes lentosTestes quebradiços

Não poder testar sem rede

introduçãoTestes que acessam rede

app/services/criador_pokemon.rb

class CriadorPokemon# ...def criar Pokemon.create(nome: nome)end

private # ... def cria_info resposta = Net::HTTP.get(endpoint) @info = JSON.parse(resposta) endend

spec/services/criador_pokemon_spec.rb

describe 'pokemon criado' do before do criador_pokemon.criar end

subject do Pokemon.last end

it 'possui o nome correto' do expect(subject.nome).to eq('Charizard') endend

webmockTestes que acessam rede

webmock: feedback rápidoTestes que acessam rede

bash

Failure/Error: CriadorPokemon.new(6) WebMock::NetConnectNotAllowedError: Real HTTP connections are disabled. Unregistered request: GET http://pokeapi.co/api/v1/pokemon/6/ with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Host'=>'pokeapi.co', 'User-Agent'=>'Ruby'}

You can stub this request with the following snippet:

stub_request(:get, "http://pokeapi.co/api/v1/pokemon/6/"). with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Host'=>'pokeapi.co', 'User-Agent'=>'Ruby'}).to_return(:status => 200, :body => "", :headers => {})

webmock: forjando a respostaTestes que acessam rede

spec/services/criador_pokemon_spec.rb

describe 'pokemon criado' do

before do body = '{' \ ' "name": "Charizard"' \ '}' stub_request(:get, 'http://pokeapi.co/api/v1/pokemon/6/') .to_return(status: 200, body: body, headers: {}) criador_pokemon.criar endend

webmock: forjando com cURLTestes que acessam rede

bash

$ curl -is http://pokeapi.co/api/v1/pokemon/6/ > \spec/fixtures/services/criador_pokemon/resposta.txt

spec/services/criador_pokemon_spec.rb

describe 'pokemon criado' do

before do caminho_arquivo = 'spec/fixtures/services/criador_pokemon/resposta.txt' arquivo_resposta = File.new(caminho_arquivo) stub_request(:get, 'http://pokeapi.co/api/v1/pokemon/6/') .to_return(arquivo_resposta) criador_pokemon.criar endend

vcrTestes que acessam rede

vcr: con!guraçãoTestes que acessam rede

spec/support/vcr.rb

VCR.configure do |c| c.cassette_library_dir = 'spec/fixtures/vcr_cassettes' c.hook_into :webmockend

vcr: feedback rápidoTestes que acessam rede

bash

Failure/Error: CriadorPokemon.new(6)VCR::Errors::UnhandledHTTPRequestError:

===================================================================== An HTTP request has been made that VCR does not know how to handle: GET http://pokeapi.co/api/v1/pokemon/6/

There is currently no cassette in use. There are a few ways you can configure VCR to handle this request:

...

vcr: forjando a respostaTestes que acessam rede

spec/services/criador_pokemon_spec.rb

describe 'pokemon criado' do before do VCR.use_cassette('CriadorPokemon/criar') do criador_pokemon.criar end end

#...

it 'possui o nome correto' do expect(subject.nome).to eq('Charizard') endend

vcr: RSpec metadataTestes que acessam rede

spec/support/vcr.rb

VCR.configure do |c|

# ... c.configure_rspec_metadata!end

spec/spec_helper.rb

RSpec.configure do |config|

# ... config.treat_symbols_as_metadata_keys_with_true_values = trueend

spec/services/criador_pokemon_spec.rb

describe 'pokemon criado', :vcr do before do criador_pokemon.criar end

#...

it 'possui o nome correto' do expect(subject.nome).to eq('Charizard') endend

factory_girl

!xtures X factoriesfactory_girl

criando uma factoryfactory_girl

spec/factories/usuarios.rb

FactoryGirl.define do factory :usuario do nome 'Mauro' email 'mauro@helabs.com.br' endend

console rails

FactoryGirl.create(:usuario)FactoryGirl.create(:usuario, email: 'mauro@helabs.com.br')

con!gurandofactory_girl

spec/spec_helper.rb

RSpec.configure do |config| # ... config.include FactoryGirl::Syntax::Methodsend

Em um teste qualquer

let!(:artigo) do create(:artigo)end

attributes_forfactory_girl

spec/controllers/posts_controller_spec.rb

describe "POST 'create'" do let(:params) do { artigo: { titulo: 'Meu titulo', conteudo: 'Conteudo do artigo' } } endend

spec/controllers/posts_controller_spec.rb

describe "POST 'create'" do let(:params) do { artigo: attributes_for(:artigo) } endend

herançafactory_girl

spec/factories/artigos.rb

factory :artigo do titulo 'Diversas dicas do RSpec' conteudo 'Conteúdo de Diversas dicas do RSpec'

factory :artigo_aprovado do aprovado true end

factory :artigo_nao_aprovado do aprovado false endend

console rails

FactoryGirl.create(:artigo_aprovado)FactoryGirl.create(:artigo_nao_aprovado)

traitsfactory_girl

spec/factories/artigos.rb

factory :artigo do titulo 'Diversas dicas do RSpec' conteudo 'Conteúdo de Diversas dicas do RSpec'

trait :aprovado do aprovado true end

trait :nao_aprovado do aprovado false endend

console rails

FactoryGirl.create(:artigo, :aprovado)FactoryGirl.create(:artigo, :nao_aprovado)

dependent attributesfactory_girl

spec/factories/artigos.rb

factory :artigo do titulo 'Diversas dicas do RSpec' conteudo { "Conteúdo do artigo #{titulo}. Aprovado: #{aprovado}" }end

sequencefactory_girl

spec/factories/artigos.rb

factory :artigo do sequence(:titulo) { |n| "Diversas dicas do RSpec #{n}" } conteudo { "Conteúdo do artigo #{titulo}. Aprovado: #{aprovado}" }end

associaçõesfactory_girl

console rails

usuario = FactoryGirl.create(:usuario)FactoryGirl.create(:artigo, usuario: usuario)

spec/factories/artigos.rb

factory :artigo do titulo 'Diversas dicas do RSpec' conteudo { "Conteúdo do artigo #{titulo}. Aprovado: #{aprovado}" } usuarioend

aliasesfactory_girl

spec/factories/artigos.rb

factory :usuario, aliases: [:autor] do nome 'Mauro' email { "#{nome}@helabs.com.br" }end

strategiesfactory_girl

console rails

pokemon = FactoryGirl.build(:pokemon)

console rails

pokemon = FactoryGirl.build_stubbed(:pokemon)

lintfactory_girl

spec/support/factory_girl.rb

RSpec.configure do |config|

config.before(:suite) do begin DatabaseCleaner.start FactoryGirl.lint ensure DatabaseCleaner.clean end endend

timecop

app/models/pokemon.rb

class Pokemon < ActiveRecord::Base

scope :escolhidos_ontem, -> do where(escolhido_em: 1.day.ago.midnight..Time.zone.now.midnight) endend

spec/models/pokemon_spec.rb

describe '.escolhidos_ontem' do let!(:pokemon_escolhido_ontem) do create(:pokemon, escolhido_em: Time.zone.local(2014, 5, 16, 10, 45)) end

subject do Pokemon.escolhidos_ontem end

it 'tem o pokemon escolhido ontem' do expect(subject).to include(pokemon_escolhido_ontem) endend

spec/models/pokemon_spec.rb

describe '.escolhidos_ontem' do

# ...

it 'tem o pokemon escolhido ontem' do Timecop.freeze(Time.zone.local((2014, 5, 17, 10, 45)) do expect(subject).to include(pokemon_escolhido_ontem) end endend

simplecov

veri!cando a coberturasimplecov

spec/spec_helper.rb

require 'simplecov'SimpleCov.start 'rails'

Primeira linha do spec_helper.rb

O falso 100%simplecov

app/models/pokemon.rb

class Pokemon < ActiveRecord::Base

validates :nome, :id_nacional, presence: true scope :escolhidos_ontem, -> do where(escolhido_em: 1.day.ago.midnight..Time.zone.now.midnight) endend

Não teste associações, validações ou escopos do Active Record

simplecov

teste associações, validações e escopos do Active Record

simplecov

devo ter 100% de cobertura de testes?simplecov

shoulda-matchers

app/models/pokemon.rb

class Pokemon < ActiveRecord::Base

validates :nome, :id_nacional, presence: true validates :id_nacional, numericality: { only_integer: true, greater_than: 0 }end

spec/models/pokemon_spec.rb

describe 'validações' do

it { should validate_presence_of(:nome) } it { should validate_presence_of(:id_nacional) } it { should validate_numericality_of(:id_nacional).only_integer .is_greater_than(0) }end

os matchersshoulda-matchers

ActiveModel

os matchersshoulda-matchers

ActiveModelActiveRecord

os matchersshoulda-matchers

ActiveModelActiveRecord

ActionController

os matchersshoulda-matchers

além do shoulda-matchersshoulda-matchers

https://github.com/bmabey/email-spec

além do shoulda-matchersshoulda-matchers

https://github.com/bmabey/email-spechttps://github.com/philostler/rspec-sidekiq

além do shoulda-matchersshoulda-matchers

https://github.com/bmabey/email-spechttps://github.com/philostler/rspec-sidekiq

https://github.com/evansagge/mongoid-rspec

além do shoulda-matchersshoulda-matchers

Obrigado!

maurogeorge.com.br

top related