Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

RSpec Best Friends @ TDC Florianópolis 2014

1,115 views

Published on

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

Published in: Technology

RSpec Best Friends @ TDC Florianópolis 2014

  1. 1. RSpec Best Friends
  2. 2. Mauro quem...
  3. 3. RSpec Best Friends
  4. 4. RSpec Best Friends
  5. 5. maurogeorge.com.br
  6. 6. RSpec
  7. 7. sintaxe de expectativa RSpec
  8. 8. 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
  9. 9. 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
  10. 10. spec/models/pokemon_spec.rb it { expect(subject).to be_a(ActiveRecord::Base) }
  11. 11. spec/models/pokemon_spec.rb it { should be_a(ActiveRecord::Base) }
  12. 12. spec/models/pokemon_spec.rb it { is_expected.to be_a(ActiveRecord::Base) } Somente RSpec 3
  13. 13. spec/spec_helper.rb RSpec.configure do |config| # ... config.expect_with :rspec do |c| c.syntax = :expect end end
  14. 14. descrevendo melhor os testes RSpec
  15. 15. 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
  16. 16. 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
  17. 17. não teste apenas o happy path RSpec
  18. 18. 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
  19. 19. 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
  20. 20. contextos para a melhor descrição RSpec
  21. 21. 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
  22. 22. de!nindo o sujeito RSpec
  23. 23. 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
  24. 24. 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
  25. 25. 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
  26. 26. utilize sempre os matchers RSpec
  27. 27. spec/models/pokemon_spec.rb it 'é nil' do expect(subject.nome_completo).to eq(nil) end
  28. 28. spec/models/pokemon_spec.rb it 'é nil' do expect(subject.nome_completo).to be_nil end
  29. 29. não use should RSpec
  30. 30. spec/models/pokemon_spec.rb it 'should have the name and the national_id' do expect(pokemon.full_name).to eq('Charizard - 6') end
  31. 31. spec/models/pokemon_spec.rb it 'does have the name and the national_id' do expect(pokemon.full_name).to eq('Charizard - 6') end
  32. 32. ordem aleatória nos testes RSpec
  33. 33. spec/spec_helper.rb RSpec.configure do |config| # ... config.order = "random" end
  34. 34. coding style RSpec
  35. 35. https://github.com/mongoid/mongoid coding style RSpec
  36. 36. https://github.com/mongoid/mongoid https://github.com/bbatsov/ruby-style-guide coding style RSpec
  37. 37. https://github.com/mongoid/mongoid https://github.com/bbatsov/ruby-style-guide https://github.com/bbatsov/rails-style-guide coding style RSpec
  38. 38. Testes que acessam rede
  39. 39. introdução Testes que acessam rede
  40. 40. Testes lentos introdução Testes que acessam rede
  41. 41. Testes lentos Testes quebradiços introdução Testes que acessam rede
  42. 42. Testes lentos Testes quebradiços Não poder testar sem rede introdução Testes que acessam rede
  43. 43. 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
  44. 44. 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
  45. 45. webmock Testes que acessam rede
  46. 46. webmock: feedback rápido Testes que acessam rede
  47. 47. 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 => {})
  48. 48. webmock: forjando a resposta Testes que acessam rede
  49. 49. 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
  50. 50. webmock: forjando com cURL Testes que acessam rede
  51. 51. bash $ curl -is http://pokeapi.co/api/v1/pokemon/6/ > spec/fixtures/services/criador_pokemon/resposta.txt
  52. 52. 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
  53. 53. vcr Testes que acessam rede
  54. 54. vcr: con!guração Testes que acessam rede
  55. 55. spec/support/vcr.rb VCR.configure do |c| c.cassette_library_dir = 'spec/fixtures/vcr_cassettes' c.hook_into :webmock end
  56. 56. vcr: feedback rápido Testes que acessam rede
  57. 57. 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: ...
  58. 58. vcr: forjando a resposta Testes que acessam rede
  59. 59. 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
  60. 60. vcr: RSpec metadata Testes que acessam rede
  61. 61. 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
  62. 62. 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
  63. 63. factory_girl
  64. 64. !xtures X factories factory_girl
  65. 65. criando uma factory factory_girl
  66. 66. spec/factories/usuarios.rb FactoryGirl.define do factory :usuario do nome 'Mauro' email 'mauro@helabs.com.br' end end
  67. 67. console rails FactoryGirl.create(:usuario) FactoryGirl.create(:usuario, email: 'mauro@helabs.com.br')
  68. 68. con!gurando factory_girl
  69. 69. spec/spec_helper.rb RSpec.configure do |config| # ... config.include FactoryGirl::Syntax::Methods end Em um teste qualquer let!(:artigo) do create(:artigo) end
  70. 70. attributes_for factory_girl
  71. 71. spec/controllers/posts_controller_spec.rb describe "POST 'create'" do let(:params) do { artigo: { titulo: 'Meu titulo', conteudo: 'Conteudo do artigo' } } end end
  72. 72. spec/controllers/posts_controller_spec.rb describe "POST 'create'" do let(:params) do { artigo: attributes_for(:artigo) } end end
  73. 73. herança factory_girl
  74. 74. 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
  75. 75. console rails FactoryGirl.create(:artigo_aprovado) FactoryGirl.create(:artigo_nao_aprovado)
  76. 76. traits factory_girl
  77. 77. 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
  78. 78. console rails FactoryGirl.create(:artigo, :aprovado) FactoryGirl.create(:artigo, :nao_aprovado)
  79. 79. dependent attributes factory_girl
  80. 80. spec/factories/artigos.rb factory :artigo do titulo 'Diversas dicas do RSpec' conteudo { "Conteúdo do artigo #{titulo}. Aprovado: #{aprovado}" } end
  81. 81. sequence factory_girl
  82. 82. spec/factories/artigos.rb factory :artigo do sequence(:titulo) { |n| "Diversas dicas do RSpec #{n}" } conteudo { "Conteúdo do artigo #{titulo}. Aprovado: #{aprovado}" } end
  83. 83. associações factory_girl
  84. 84. console rails usuario = FactoryGirl.create(:usuario) FactoryGirl.create(:artigo, usuario: usuario)
  85. 85. spec/factories/artigos.rb factory :artigo do titulo 'Diversas dicas do RSpec' conteudo { "Conteúdo do artigo #{titulo}. Aprovado: #{aprovado}" } usuario end
  86. 86. aliases factory_girl
  87. 87. spec/factories/artigos.rb factory :usuario, aliases: [:autor] do nome 'Mauro' email { "#{nome}@helabs.com.br" } end
  88. 88. strategies factory_girl
  89. 89. console rails pokemon = FactoryGirl.build(:pokemon)
  90. 90. console rails pokemon = FactoryGirl.build_stubbed(:pokemon)
  91. 91. lint factory_girl
  92. 92. spec/support/factory_girl.rb RSpec.configure do |config| config.before(:suite) do begin DatabaseCleaner.start FactoryGirl.lint ensure DatabaseCleaner.clean end end end
  93. 93. timecop
  94. 94. 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
  95. 95. 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
  96. 96. 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
  97. 97. simplecov
  98. 98. veri!cando a cobertura simplecov
  99. 99. spec/spec_helper.rb require 'simplecov' SimpleCov.start 'rails' Primeira linha do spec_helper.rb
  100. 100. O falso 100% simplecov
  101. 101. 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
  102. 102. Não teste associações, validações ou escopos do Active Record simplecov
  103. 103. teste associações, validações e escopos do Active Record simplecov
  104. 104. devo ter 100% de cobertura de testes? simplecov
  105. 105. shoulda-matchers
  106. 106. 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
  107. 107. 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
  108. 108. os matchers shoulda-matchers
  109. 109. ActiveModel os matchers shoulda-matchers
  110. 110. ActiveModel ActiveRecord os matchers shoulda-matchers
  111. 111. ActiveModel ActiveRecord ActionController os matchers shoulda-matchers
  112. 112. além do shoulda-matchers shoulda-matchers
  113. 113. https://github.com/bmabey/email-spec além do shoulda-matchers shoulda-matchers
  114. 114. https://github.com/bmabey/email-spec https://github.com/philostler/rspec-sidekiq além do shoulda-matchers shoulda-matchers
  115. 115. 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
  116. 116. Obrigado!
  117. 117. maurogeorge.com.br

×