Se ha denunciado esta presentación.
Se está descargando tu SlideShare. ×

Effectively Testing Services on Rails - Railsconf 2014

Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Próximo SlideShare
Mashups & APIs
Mashups & APIs
Cargando en…3
×

Eche un vistazo a continuación

1 de 76 Anuncio

Más Contenido Relacionado

Presentaciones para usted (19)

Similares a Effectively Testing Services on Rails - Railsconf 2014 (20)

Anuncio

Más reciente (20)

Effectively Testing Services on Rails - Railsconf 2014

  1. 1. Effectively Testing Services Neal Kemp
  2. 2. $ whoami Iowa native Now: Californian Software Developer Independent Consultant
  3. 3. What I Do Ruby / Rails Javascript / Angular HTML, CSS, etc
  4. 4. what,why&how of testing services
  5. 5. NOT Building testable services
  6. 6. NOT Test-driven development (necessarily)  
  7. 7. … and because I don’t want @dhh to rage
  8. 8. what
  9. 9. What is a service? Internal “SOA”
  10. 10. Any time you make an HTTP request to an endpoint in another repository
  11. 11. why
  12. 12. Why are services important? Build faster Makes scaling easier Use them on virtually every application Increasingly prevalent
  13. 13. Services are critical to modern Rails development
  14. 14. Why is testing services important? You (should) test everything else Services compose crucial features You may encounter problems…
  15. 15. Internal API Sometimes null responses Inconsistencies Catastrophe
  16. 16. Okay? But what about external APIs?
  17. 17. {"id": 24} {"code": "ANA"}
  18. 18. "goals":[ { "per":"1", "ta":"CGY", "et":"14:11", "st":"Wrist Shot" }, { "per":"2", "ta":"ANA", "et":"11:12", "st":"Backhand" } ] "goals": { "per":"1", "ta":"CGY", "et":"14:11", "st":"Wrist Shot" }
  19. 19. No versioning!
  20. 20. Snapchat Client Haphazard documentation What are the requests? Bizarre obfuscation github.com/nneal/snapcat
  21. 21. how
  22. 22. What is different about services? External network requests You don’t own the code
  23. 23. On an airplane… Failure is bad! No network requests
  24. 24. Don’t interact with services from test environment* **
  25. 25. * Includes “dummy” APIs
  26. 26. ** Using pre-recorded responses is okay
  27. 27. Assuming: Rails, rspec
  28. 28. Timetostub!
  29. 29. Built-in Stubbing Typhoeus Faraday Excon
  30. 30. Simplify.
  31. 31. gem 'webmock'
  32. 32. ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rspec/autorun' require 'rspec/rails’ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) RSpec.configure do |config| config.infer_base_class_for_anonymous_controllers = false config.order = 'random’ end WebMock.disable_net_connect! spec/spec_helper.rb
  33. 33. module FacebookWrapper def self.user_id(username) user_data(username)['id'] end def self.user_data(username) JSON.parse( open("https://graph.facebook.com/#{username}").read ) end end lib/facebook_wrapper.rb
  34. 34. require 'facebook_wrapper' config/intializers/facebook_wrapper.rb
  35. 35. require 'spec_helper' describe FacebookWrapper, '.user_link' do it 'retrieves user link' do stub_request(:get, 'https://graph.facebook.com/arjun'). to_return( status: 200, headers: {}, body: '{ "id": "7901103","first_name": "Arjun", "locale": "en_US","username": "Arjun" }' ) user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end spec/lib/facebook_wrapper_spec.rb
  36. 36. require 'spec_helper' describe FacebookWrapper, '.user_link' do it 'retrieves user link' do stub_request(:get, 'https://graph.facebook.com/arjun'). to_return( status: 200, headers: {}, body: '{ "id": "7901103","first_name": "Arjun", "locale": "en_US","username": "Arjun" }' ) user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end spec/lib/facebook_wrapper_spec.rb
  37. 37. require 'spec_helper' describe FacebookWrapper, '.user_link' do it 'retrieves user link' do stub_request(:get, 'https://graph.facebook.com/arjun'). to_return( status: 200, headers: {}, body: '{ "id": "7901103","first_name": "Arjun", "locale": "en_US","username": "Arjun" }' ) user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end spec/lib/facebook_wrapper_spec.rb
  38. 38. require 'spec_helper' describe FacebookWrapper, '.user_link' do it 'retrieves user link' do stub_request(:get, 'https://graph.facebook.com/arjun'). to_return( status: 200, headers: {}, body: '{ "id": "7901103","first_name": "Arjun", "locale": "en_US","username": "Arjun" }' ) user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end spec/lib/facebook_wrapper_spec.rb
  39. 39. Even Better No network requests Fast! No intermittent failure
  40. 40. Mock-Services AWS FB graph mock OmniAuth Etc…
  41. 41. gem 'fb_graph-mock'
  42. 42. ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rspec/autorun' require 'rspec/rails’ require 'fb_graph/mock' Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) RSpec.configure do |config| config.infer_base_class_for_anonymous_controllers = false config.order = 'random' config.include FbGraph::Mock end WebMock.disable_net_connect! spec/spec_helper.rb
  43. 43. describe FacebookWrapper, '.user_link' do it 'retrieves user link' do mock_graph :get, 'arjun', 'users/arjun_public' do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end end spec/lib/facebook_wrapper_spec.rb
  44. 44. describe FacebookWrapper, '.user_link' do it 'retrieves user link' do mock_graph :get, 'arjun', 'users/arjun_public' do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end end spec/lib/facebook_wrapper_spec.rb
  45. 45. Even Better Already stubbed for you Pre-recorded responses (sometimes) Don’t need to know API endpoints
  46. 46. gem 'sham_rack'
  47. 47. gem 'sinatra'
  48. 48. ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rspec/autorun' require 'rspec/rails’ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) RSpec.configure do |config| config.infer_base_class_for_anonymous_controllers = false config.order = 'random’ end WebMock.disable_net_connect! spec/spec_helper.rb
  49. 49. ShamRack.at('graph.facebook.com', 443).sinatra do get '/:username' do %Q|{ "id": "7901103", "name": "Arjun Banker", "first_name": "Arjun", "last_name": "Banker", "link": "http://www.facebook.com/#{params[:username]}", "location": { "id": 114952118516947, "name": "San Francisco, California" }, "gender": "male" }| end end spec/support/fake_facebook.rb
  50. 50. ShamRack.at('graph.facebook.com', 443).sinatra do get '/:username' do %Q|{ "id": "7901103", "name": "Arjun Banker", "first_name": "Arjun", "last_name": "Banker", "link": "http://www.facebook.com/#{params[:username]}", "location": { "id": 114952118516947, "name": "San Francisco, California" }, "gender": "male" }| end end spec/support/fake_facebook.rb
  51. 51. ShamRack.at('graph.facebook.com', 443).sinatra do get '/:username' do %Q|{ "id": "7901103", "name": "Arjun Banker", "first_name": "Arjun", "last_name": "Banker", "link": "http://www.facebook.com/#{params[:username]}", "location": { "id": 114952118516947, "name": "San Francisco, California" }, "gender": "male" }| end end spec/support/fake_facebook.rb
  52. 52. ShamRack.at('graph.facebook.com', 443).sinatra do get '/:username' do %Q|{ "id": "7901103", "name": "Arjun Banker", "first_name": "Arjun", "last_name": "Banker", "link": "http://www.facebook.com/#{params[:username]}", "location": { "id": 114952118516947, "name": "San Francisco, California" }, "gender": "male" }| end end spec/support/fake_facebook.rb
  53. 53. describe FacebookWrapper, '.user_link' do it 'retrieves user link' do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103’ end end spec/lib/facebook_wrapper_spec.rb
  54. 54. Even Better Dynamic Expressive Readable
  55. 55. gem 'vcr'
  56. 56. ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rspec/autorun' require 'rspec/rails’ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) RSpec.configure do |config| config.infer_base_class_for_anonymous_controllers = false config.order = 'random’ end WebMock.disable_net_connect! VCR.configure do |c| c.cassette_library_dir = 'spec/fixtures/vcr_cassettes' c.hook_into :webmock end spec/spec_helper.rb
  57. 57. describe FacebookWrapper, '.user_link' do it 'retrieves user link' do VCR.use_cassette('fb_user_arjun') do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end end spec/lib/facebook_wrapper_spec.rb
  58. 58. describe FacebookWrapper, '.user_link' do it 'retrieves user link' do VCR.use_cassette('fb_user_arjun') do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end end spec/lib/facebook_wrapper_spec.rb
  59. 59. Even Better Record API automatically Replay responses without network Verify responses
  60. 60. Additional Build Process Runs outside normal test mode Rechecks cassettes for diffs Avoids versioning issues
  61. 61. gem 'puffing-billy'
  62. 62. Puffing-Billy Built for in-browser requests Allowed to record and reuse (like VCR)
  63. 63. Be brave, venture out of ruby
  64. 64. I also like…
  65. 65. Chrome Dev Tools
  66. 66. Postman
  67. 67. HTTPie
  68. 68. Charles
  69. 69. Additional Reading martinfowler.com/bliki/IntegrationContractTest.html robots.thoughtbot.com/how-to-stub-external-services-in-tests joblivious.wordpress.com/2009/02/20/handling-intermittence-how-to- survive-test-driven-development railscasts.com/episodes/291-testing-with-vcr
  70. 70. Bringing it all together Testing services is crucial If in doubt, stub it out Determine the flexibility you want Record responses to save time
  71. 71. Next Up Eliminating Inconsistent Test Failures with Austin Putman
  72. 72. Thank you! me@nealke.mp (I like emails) @neal_kemp (I tweet)

Notas del editor

  • Includes: things like Stripe, or an internal API, or an iPhone app calling into an API exposed by your rails app
  • LA kings checkingchicagoblackhawks
  • Can back with yaml
  • Can back with yaml
  • Can back with yaml
  • Can back with yaml
  • Re-writing web proxyAllowed to record and reuse (like VCR)
  • Can back with yaml
  • Ubiquitous PowerfulIn-browser (so easy!)
  • Easily send requestsEasy-to-use GUI
  • Postman without the GUICould run small scripts around it
  • re-writing web proxyTest mobile as well as desktopGood for collecting a lot of responsesGood for testing things that aren’t specific page loads in ChromeGood when you don’t know what’s even being requested!

×