SlideShare uma empresa Scribd logo
1 de 122
Baixar para ler offline
RSpec Best Friends
Mauro quem...
RSpec Best
Friends
RSpec Best
Friends
maurogeorge.com.br
RSpec
sintaxe de expectativa
RSpec
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
end
end
descrevendo melhor os testes
RSpec
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')
end
end
não teste apenas o happy path
RSpec
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')
end
end
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
end
end
contextos para a melhor descrição
RSpec
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
end
end
de!nindo o sujeito
RSpec
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')
end
end
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')
end
end
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')
end
end
utilize sempre os matchers
RSpec
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_nil
end
não use should
RSpec
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 testes
RSpec
spec/spec_helper.rb
RSpec.configure do |config|
# ...
config.order = "random"
end
coding style
RSpec
https://github.com/mongoid/mongoid
coding style
RSpec
https://github.com/mongoid/mongoid
https://github.com/bbatsov/ruby-style-guide
coding style
RSpec
https://github.com/mongoid/mongoid
https://github.com/bbatsov/ruby-style-guide
https://github.com/bbatsov/rails-style-guide
coding style
RSpec
Testes que acessam rede
introdução
Testes que acessam rede
Testes lentos
introdução
Testes que acessam rede
Testes lentos
Testes quebradiços
introdução
Testes que acessam rede
Testes lentos
Testes quebradiços
Não poder testar sem rede
introdução
Testes 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)
end
end
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')
end
end
webmock
Testes que acessam rede
webmock: feedback rápido
Testes 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 resposta
Testes 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
end
end
webmock: forjando com cURL
Testes 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
end
end
vcr
Testes que acessam rede
vcr: con!guração
Testes que acessam rede
spec/support/vcr.rb
VCR.configure do |c|
c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
c.hook_into :webmock
end
vcr: feedback rápido
Testes 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 resposta
Testes 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')
end
end
vcr: RSpec metadata
Testes 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 = true
end
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')
end
end
factory_girl
!xtures X factories
factory_girl
criando uma factory
factory_girl
spec/factories/usuarios.rb
FactoryGirl.define do
factory :usuario do
nome 'Mauro'
email 'mauro@helabs.com.br'
end
end
console rails
FactoryGirl.create(:usuario)
FactoryGirl.create(:usuario, email: 'mauro@helabs.com.br')
con!gurando
factory_girl
spec/spec_helper.rb
RSpec.configure do |config|
# ...
config.include FactoryGirl::Syntax::Methods
end
Em um teste qualquer
let!(:artigo) do
create(:artigo)
end
attributes_for
factory_girl
spec/controllers/posts_controller_spec.rb
describe "POST 'create'" do
let(:params) do
{
artigo: {
titulo: 'Meu titulo',
conteudo: 'Conteudo do artigo'
}
}
end
end
spec/controllers/posts_controller_spec.rb
describe "POST 'create'" do
let(:params) do
{ artigo: attributes_for(:artigo) }
end
end
herança
factory_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
end
end
console rails
FactoryGirl.create(:artigo_aprovado)
FactoryGirl.create(:artigo_nao_aprovado)
traits
factory_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
end
end
console rails
FactoryGirl.create(:artigo, :aprovado)
FactoryGirl.create(:artigo, :nao_aprovado)
dependent attributes
factory_girl
spec/factories/artigos.rb
factory :artigo do
titulo 'Diversas dicas do RSpec'
conteudo { "Conteúdo do artigo #{titulo}. Aprovado: #{aprovado}" }
end
sequence
factory_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ções
factory_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}" }
usuario
end
aliases
factory_girl
spec/factories/artigos.rb
factory :usuario, aliases: [:autor] do
nome 'Mauro'
email { "#{nome}@helabs.com.br" }
end
strategies
factory_girl
console rails
pokemon = FactoryGirl.build(:pokemon)
console rails
pokemon = FactoryGirl.build_stubbed(:pokemon)
lint
factory_girl
spec/support/factory_girl.rb
RSpec.configure do |config|
config.before(:suite) do
begin
DatabaseCleaner.start
FactoryGirl.lint
ensure
DatabaseCleaner.clean
end
end
end
timecop
app/models/pokemon.rb
class Pokemon < ActiveRecord::Base
scope :escolhidos_ontem, -> do
where(escolhido_em: 1.day.ago.midnight..Time.zone.now.midnight)
end
end
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)
end
end
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
end
end
simplecov
veri!cando a cobertura
simplecov
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)
end
end
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 matchers
shoulda-matchers
ActiveModel
os matchers
shoulda-matchers
ActiveModel
ActiveRecord
os matchers
shoulda-matchers
ActiveModel
ActiveRecord
ActionController
os matchers
shoulda-matchers
além do shoulda-matchers
shoulda-matchers
https://github.com/bmabey/email-spec
além do shoulda-matchers
shoulda-matchers
https://github.com/bmabey/email-spec
https://github.com/philostler/rspec-sidekiq
além do shoulda-matchers
shoulda-matchers
https://github.com/bmabey/email-spec
https://github.com/philostler/rspec-sidekiq
https://github.com/evansagge/mongoid-rspec
além do shoulda-matchers
shoulda-matchers
Obrigado!
maurogeorge.com.br

Mais conteúdo relacionado

Destaque

A Análise de Conteúdo em Plataformas de Interação Online: Aplicação Comparati...
A Análise de Conteúdo em Plataformas de Interação Online: Aplicação Comparati...A Análise de Conteúdo em Plataformas de Interação Online: Aplicação Comparati...
A Análise de Conteúdo em Plataformas de Interação Online: Aplicação Comparati...Kadu Fernandiz
 
Best of Esomar Belgium 2010_Flores
Best of Esomar Belgium 2010_FloresBest of Esomar Belgium 2010_Flores
Best of Esomar Belgium 2010_FloresSchillewaert Niels
 
Best Practices for Online Longitudinal Qualitative Research
Best Practices for Online Longitudinal Qualitative ResearchBest Practices for Online Longitudinal Qualitative Research
Best Practices for Online Longitudinal Qualitative Researchlvanpatten
 
NETNOGRAPHY vs. Web Monitoring = “Qual. VS Quant. ?”
NETNOGRAPHY vs. Web Monitoring = “Qual. VS Quant. ?”NETNOGRAPHY vs. Web Monitoring = “Qual. VS Quant. ?”
NETNOGRAPHY vs. Web Monitoring = “Qual. VS Quant. ?”Steffen Hück
 
TDC2016SP - Trilha Análise de Negócios
TDC2016SP - Trilha Análise de NegóciosTDC2016SP - Trilha Análise de Negócios
TDC2016SP - Trilha Análise de Negóciostdc-globalcode
 

Destaque (6)

A Análise de Conteúdo em Plataformas de Interação Online: Aplicação Comparati...
A Análise de Conteúdo em Plataformas de Interação Online: Aplicação Comparati...A Análise de Conteúdo em Plataformas de Interação Online: Aplicação Comparati...
A Análise de Conteúdo em Plataformas de Interação Online: Aplicação Comparati...
 
Best of Esomar Belgium 2010_Flores
Best of Esomar Belgium 2010_FloresBest of Esomar Belgium 2010_Flores
Best of Esomar Belgium 2010_Flores
 
Best Practices for Online Longitudinal Qualitative Research
Best Practices for Online Longitudinal Qualitative ResearchBest Practices for Online Longitudinal Qualitative Research
Best Practices for Online Longitudinal Qualitative Research
 
NETNOGRAPHY vs. Web Monitoring = “Qual. VS Quant. ?”
NETNOGRAPHY vs. Web Monitoring = “Qual. VS Quant. ?”NETNOGRAPHY vs. Web Monitoring = “Qual. VS Quant. ?”
NETNOGRAPHY vs. Web Monitoring = “Qual. VS Quant. ?”
 
Palestra Engajamento 2.0: Redes sociais corporativas
Palestra Engajamento 2.0: Redes sociais corporativasPalestra Engajamento 2.0: Redes sociais corporativas
Palestra Engajamento 2.0: Redes sociais corporativas
 
TDC2016SP - Trilha Análise de Negócios
TDC2016SP - Trilha Análise de NegóciosTDC2016SP - Trilha Análise de Negócios
TDC2016SP - Trilha Análise de Negócios
 

Mais de Mauro George

Aprendendo com projetos open source @ RubyConf 2015
Aprendendo com projetos open source @ RubyConf 2015Aprendendo com projetos open source @ RubyConf 2015
Aprendendo com projetos open source @ RubyConf 2015Mauro George
 
Rails front-end com bourbon e sua familia @ Front in Maceió 2014
Rails front-end com bourbon e sua familia @ Front in Maceió 2014Rails front-end com bourbon e sua familia @ Front in Maceió 2014
Rails front-end com bourbon e sua familia @ Front in Maceió 2014Mauro George
 
Testes automatizados o time e o cliente saem ganhando! @ Agile Vale 2014
Testes automatizados o time e o cliente saem ganhando! @ Agile Vale 2014Testes automatizados o time e o cliente saem ganhando! @ Agile Vale 2014
Testes automatizados o time e o cliente saem ganhando! @ Agile Vale 2014Mauro George
 
O cliente e o time juntos por um só objetivo! @ CONADEV 2014
O cliente e o time juntos por um só objetivo! @ CONADEV 2014O cliente e o time juntos por um só objetivo! @ CONADEV 2014
O cliente e o time juntos por um só objetivo! @ CONADEV 2014Mauro George
 
Model of the colossus @ Rupy Brazil 2013
Model of the colossus @ Rupy Brazil 2013 Model of the colossus @ Rupy Brazil 2013
Model of the colossus @ Rupy Brazil 2013 Mauro George
 
Model of the colossus @ Café com Dev
Model of the colossus @ Café com DevModel of the colossus @ Café com Dev
Model of the colossus @ Café com DevMauro George
 
Git para iniciantes v1.3.0 @ PHP Conference Brasil 2012
Git para iniciantes v1.3.0 @ PHP Conference Brasil 2012Git para iniciantes v1.3.0 @ PHP Conference Brasil 2012
Git para iniciantes v1.3.0 @ PHP Conference Brasil 2012Mauro George
 

Mais de Mauro George (7)

Aprendendo com projetos open source @ RubyConf 2015
Aprendendo com projetos open source @ RubyConf 2015Aprendendo com projetos open source @ RubyConf 2015
Aprendendo com projetos open source @ RubyConf 2015
 
Rails front-end com bourbon e sua familia @ Front in Maceió 2014
Rails front-end com bourbon e sua familia @ Front in Maceió 2014Rails front-end com bourbon e sua familia @ Front in Maceió 2014
Rails front-end com bourbon e sua familia @ Front in Maceió 2014
 
Testes automatizados o time e o cliente saem ganhando! @ Agile Vale 2014
Testes automatizados o time e o cliente saem ganhando! @ Agile Vale 2014Testes automatizados o time e o cliente saem ganhando! @ Agile Vale 2014
Testes automatizados o time e o cliente saem ganhando! @ Agile Vale 2014
 
O cliente e o time juntos por um só objetivo! @ CONADEV 2014
O cliente e o time juntos por um só objetivo! @ CONADEV 2014O cliente e o time juntos por um só objetivo! @ CONADEV 2014
O cliente e o time juntos por um só objetivo! @ CONADEV 2014
 
Model of the colossus @ Rupy Brazil 2013
Model of the colossus @ Rupy Brazil 2013 Model of the colossus @ Rupy Brazil 2013
Model of the colossus @ Rupy Brazil 2013
 
Model of the colossus @ Café com Dev
Model of the colossus @ Café com DevModel of the colossus @ Café com Dev
Model of the colossus @ Café com Dev
 
Git para iniciantes v1.3.0 @ PHP Conference Brasil 2012
Git para iniciantes v1.3.0 @ PHP Conference Brasil 2012Git para iniciantes v1.3.0 @ PHP Conference Brasil 2012
Git para iniciantes v1.3.0 @ PHP Conference Brasil 2012
 

RSpec Best Friends @ TDC Florianópolis 2014