Straight Up RSpec 3 - a neat Ruby BDD tool

1,398 views

Published on

Upgrading, going beyond the basics, introducing some elegant features and promoting readability.

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,398
On SlideShare
0
From Embeds
0
Number of Embeds
202
Actions
Shares
0
Downloads
12
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Straight Up RSpec 3 - a neat Ruby BDD tool

  1. 1. Straight Up RSpec 3 a neat Ruby BDD tool
  2. 2. Organization $0 Running Specs $0 Configuration $0 Refactoring Exercise $0 Other Libraries $0 The Menu
  3. 3. Organization
  4. 4. .rspec lib/… spec/spec_helper.rb spec/support/matchers/drink.rb spec/martini_spec.rb
  5. 5. spec_helper.rb require 'barkeep' ! Dir["#{File.dirname(__FILE__)}/support/**/*.rb"]. each {|file| require file } spec_helper
  6. 6. Running Specs
  7. 7. .rspec --color --warnings --require spec_helper .rspec
  8. 8. .rspec-local —-format documentation --backtrace .rspec-local
  9. 9. $ rspec spec/martini_spec.rb:15:22
  10. 10. $ rspec --format documentation
  11. 11. $ rspec --fail-fast
  12. 12. $ rspec --tag js:true
  13. 13. $ rspec --profile … ! Top 10 slowest examples (0.00226 seconds, 57.2% of total time): Martini with a mixer #ingredients should include "vermouth" 0.00058 seconds ./spec/martini_spec.rb:15 InitializeWithAttrsSpec with attributes #pages should eq 123 0.00052 seconds ./spec/initialize_with_attrs_spec.rb:16 …
  14. 14. $ rspec --order random … Randomized with seed 63958
  15. 15. $ rspec --order random:63958
  16. 16. martini_spec.rb describe "Martini" do … it "should have ingredients", :focus do expect(martini).to respond_to(:ingredients) end … end :focus
  17. 17. martini_spec.rb describe "Martini" do … fit "should have ingredients" do expect(martini).to respond_to(:ingredients) end … end fit
  18. 18. martini_spec.rb describe "Martini" do … it "should have ingredients", :skip do expect(martini).to respond_to(:ingredients) end … end :skip
  19. 19. martini_spec.rb describe "Martini" do … xit "should have ingredients" do expect(martini).to respond_to(:ingredients) end … end xit
  20. 20. martini_spec.rb describe "Martini" do … pending "should have price" do expect(@martini).to be_pricey end … end pending
  21. 21. Configuration
  22. 22. support/configure.rb RSpec.configure do |config| config.include MyMartiniMatchers config.include ModelHelpers, :type => :model end ! ! # spec/model/martini_spec.rb ! describe Martini, :type => :model do … end RSpec.configure
  23. 23. support/configure.rb RSpec.configure do |c| c.before(:suite) {} # once c.before(:context) {} # once before each group c.before(:example) {} # once before each example ! c.after(:example) {} # once after each example c.after(:context) {} # once after each group c.after(:suite) {} # once ! # run before each example of type :model config.before(:example, :type => :model) {} end RSpec.configure
  24. 24. support/configure.rb RSpec.configure do |c| c.filter_run focus: true c.run_all_when_everything_filtered = true end ! # in any spec file describe "thing" do it "does something interesting", :focus do # .... end end Inclusion
  25. 25. support/configure.rb RSpec.configure do |c| c.exclusion_filter = { :ruby => lambda { |version| !(RUBY_VERSION.to_s =~ /^#{version.to_s}/) }} end ! # in any spec file describe "something" do it "does something", :ruby => 1.8 do # .... end it "does something", :ruby => 2.1 do # .... Exclusion
  26. 26. Barkeep github.com/gsterndale/barkeep an upgrade & refactoring exercise
  27. 27. martini_spec.rb describe "Barkeep::Martini" do before do @martini = Barkeep::Martini.new end it "should have an attribute named booze" do @martini.should respond_to(:booze) @martini.should respond_to(:booze=) end it "should have an attribute named garnish" do @martini.should respond_to(:garnish) @martini.should respond_to(:garnish=) end end Original Martini 2.X Spec
  28. 28. martini_spec.rb describe "Barkeep::Martini" do before do @martini = Barkeep::Martini.new end it "should have an attribute named booze" do expect(@martini).to respond_to(:booze) expect(@martini).to respond_to(:booze=) end it "should have an attribute named garnish" do expect(@martini).to respond_to(:garnish) expect(@martini).to respond_to(:garnish=) end end Martini Spec 3.0
  29. 29. have_attribute.rb module AttributeMatchers ! Spec::Matchers.define :have_attribute do |name| match do |target| getter = name.to_sym setter = (name.to_s + "=").to_sym target.respond_to?(getter) && target.respond_to?(setter) end end ! end Custom Matcher
  30. 30. martini_spec.rb describe "Barkeep::Martini" do include AttributeMatchers before do @martini = Barkeep::Martini.new end it "should have an attribute named booze" do expect(@martini).to have_attribute(:booze) end it "should have an attribute named garnish" do expect(@martini).to have_attribute(:garnish) end end Custom Matcher
  31. 31. martini_spec.rb describe "Barkeep::Martini" do include AttributeMatchers subject { Barkeep::Martini.new } it "should have an attribute named booze" do expect(subject).to have_attribute(:booze) end it "should have an attribute named garnish" do expect(subject).to have_attribute(:garnish) end end subject()
  32. 32. martini_spec.rb describe "Barkeep::Martini" do include AttributeMatchers subject(:martini) { Barkeep::Martini.new } it "should have an attribute named booze" do expect(martini).to have_attribute(:booze) end it "should have an attribute named garnish" do expect(martini).to have_attribute(:garnish) end end Named subject()
  33. 33. martini_spec.rb describe "Barkeep::Martini" do include AttributeMatchers subject { Barkeep::Martini.new } it "should have an attribute named booze" do is_expected.to have_attribute(:booze) end it "should have an attribute named garnish" do is_expected.to have_attribute(:garnish) end end Implicit subject()
  34. 34. martini_spec.rb describe "Barkeep::Martini" do include AttributeMatchers subject { Barkeep::Martini.new } it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:garnish) } end DRY Messages
  35. 35. martini_spec.rb describe Barkeep::Martini do include AttributeMatchers it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:garnish) } end Derived subject()
  36. 36. have_attribute.rb module AttributeMatchers … end ! RSpec.configure do |config| config.include AttributeMatchers end config.include()
  37. 37. martini_spec.rb describe Barkeep::Martini do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:garnish) } end config.include()
  38. 38. martini_spec.rb describe "Barkeep::Martini" do before do @martini = Barkeep::Martini.new end it "should have an attribute named booze" do @martini.should respond_to(:booze) @martini.should respond_to(:booze=) end it "should have an attribute named garnish" do @martini.should respond_to(:garnish) @martini.should respond_to(:garnish=) end end Original Martini 2.X Spec
  39. 39. martini_spec.rb describe Barkeep::Martini do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:garnish) } end Refactored Martini 3.0 Spec
  40. 40. whiskey_spec.rb describe Barkeep::Whiskey do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:glass) } end New Whiskey Spec
  41. 41. martini_spec.rb describe Barkeep::Martini do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:glass) } it { is_expected.to have_attribute(:garnish) } end Additions to Martini Spec
  42. 42. support/examples/drink.rb shared_examples_for "a drink" do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:glass) } end Shared Example Group
  43. 43. whiskey_spec.rb describe Barkeep::Whiskey do it_behaves_like "a drink" end Whiskey Spec
  44. 44. whiskey_spec.rb describe Barkeep::Whiskey do include_examples "a drink" end Whiskey Spec
  45. 45. martini_spec.rb describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) } end Martini Spec
  46. 46. support/examples/drink.rb shared_examples_for "a drink" do |ingredients| it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:glass) } ! describe "#ingredients" do subject { super().ingredients } it { is_expected.to be_a Array } it { is_expected to include *ingredients } end end Whiskey & Martini #ingredients
  47. 47. martini_spec.rb describe Barkeep::Martini do it_behaves_like "a drink", "vodka" it { is_expected.to have_attribute(:garnish) } end Martini Spec
  48. 48. martini_spec.rb describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) } it { is_expected.to have_attribute(:mixer) } context "with a mixer" do before do @mixer = 'vermouth' subject.mixer = @mixer end it "should include mixer in ingredients" do expect(subject.ingredients).to include @mixer end end context()
  49. 49. martini_spec.rb describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) } it { is_expected.to have_attribute(:mixer) } context "with a mixer" do let(:mixer) { 'vermouth' } before { subject.mixer = mixer } ! it "should include mixer in ingredients" do expect(subject.ingredients).to include mixer end end end let()
  50. 50. martini_spec.rb describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) } it { is_expected.to have_attribute(:mixer) } context "with a mixer" do let(:mixer) { 'vermouth' } before { subject.mixer = mixer } ! its(:ingredients) { is_expected.to include mixer } end end its()
  51. 51. martini_spec.rb describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) } it { is_expected.to have_attribute(:mixer) } end ! describe Barkeep::Martini, ".new with mixer" do let(:mixer) { 'vermouth' } subject { Barkeep::Martini.new(mixer: mixer) } its(:ingredients) { is_expected.to include mixer } end its()
  52. 52. martini_spec.rb describe Barkeep::Martini, ".new with mixer" do let(:mixer) { 'vermouth' } subject { Barkeep::Martini.new(:mixer => mixer) } its(:ingredients) { is_expected.to include mixer } its(:ingredients) { is_expected.to have(1).items } end have()
  53. 53. martini_spec.rb describe Barkeep::Martini, ".new with mixer" do let(:mixer) { 'vermouth' } subject { Barkeep::Martini.new(:mixer => mixer) } its(:ingredients) { is_expected.to include mixer } it { is_expected.to have(1).ingredients } end have()
  54. 54. martini_spec.rb describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients } ! it { is_expected.to include 'juice', 'vodka', 'olives', 'vermouth' } ! it { is_expected.to contain_exactly 'vodka', 'vermouth', 'juice', 'olives' } end Array Matchers
  55. 55. martini_spec.rb describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients } ! it { is_expected.to start_with 'vodka' } it { is_expected.to end_with 'olives' } end Array Matchers
  56. 56. martini_spec.rb describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients } ! it { is_expected.to all be_a(String) } it { is_expected.to all start_with(/^w/) } end Array Matchers
  57. 57. martini_spec.rb describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients } ! it { is_expected.to all be_a(String) & start_with(/^w/) } end Compound Matchers
  58. 58. martini_spec.rb describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients } ! it { is_expected.to match [ start_with(/^w/), match(/olives?/) ] } end Composable Matchers
  59. 59. martini_spec.rb describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients } ! it { is_expected.to match [ a_string_starting_with(/^w/), a_string_matching(/olives?/) ] } end Composable Matchers
  60. 60. terminal_spec.rb describe Terminal, "#printer" do let(:epson) { stub "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } subject { terminal.printer } ! before do Printer.stub(:by_ip).and_return(epson) end ! it { should eq epson } end RSpec 2.X doubles
  61. 61. terminal_spec.rb describe Terminal, "#printer" do let(:epson) { double "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } subject { terminal.printer } ! before do allow(Printer).to receive_messages(by_ip: epson) end ! it { is_expected.to eq epson } end RSpec 3.0 doubles
  62. 62. terminal_spec.rb describe Terminal, "#printer" do let(:epson) { double "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } subject { terminal.printer } ! before do allow(Printer).to receive_messages(by_ID: epson) end ! it { is_expected.to eq epson } end Double checking™
  63. 63. terminal_spec.rb describe Terminal, "#printer" do let(:epson) { stub "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } subject { terminal.printer } ! before do Printer.should_receive(:by_ip) .with("1.1.1.1").and_return(epson) end ! it { should eq epson } end RSpec 2.X message expectation
  64. 64. terminal_spec.rb describe Terminal, "#printer" do let(:epson) { double "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } before do allow(Printer).to receive_messages(by_ip: epson) end subject! { terminal.printer } ! it { is_expected.to eq epson } it "finds printer by IP" do expect(Printer).to have_received(:by_ip).with("1.1.1.1") end RSpec 3.0 message expectation
  65. 65. Links
  66. 66. support/configure.rb Its: github.com/rspec/rspec-its ! Collection Matchers: github.com/rspec/rspec-collection_matchers ! RSpec Rails: github.com/rspec/rspec-rails ! ActiveModel mocks: github.com/rspec/rspec-activemodel-mocks Libraries
  67. 67. support/configure.rb Step-by-step upgrade instructions: relishapp.com/rspec/docs/upgrade ! RDoc: rubydoc.info/github/rspec/rspec-expectations ! Features as documentation: relishapp.com/rspec Documentation
  68. 68. support/configure.rb Transpec, the RSpec syntax conversion tool: yujinakayama.me/transpec/ ! Myron Marston’s blog: myronmars.to Resources
  69. 69. CHEERS
  70. 70. spec/user_spec.rb describe User, ".admin_names" do let(:admin) { User.create!(:admin => true, :first => "Clark", :last => "Kent")} let(:avg_joe) { User.create!(:admin => false, :first => "Joe", :last => "Shmo")} subject { admin_names } ! it { should include "Clark Kent" } it { should_not include "Joe Shmo" } end let() gotcha
  71. 71. spec/user_spec.rb describe User, ".admin_names" do let(:admin) { User.create!(:admin => true, :first => "Clark", :last => "Kent")} let(:avg_joe) { User.create!(:admin => false, :first => "Joe", :last => "Shmo")} subject { User.admin_names } before { admin && avg_joe } ! it { should include "Clark Kent" } it { should_not include "Joe Shmo" } end let() work-around
  72. 72. spec/user_spec.rb describe User, ".admin_names" do let!(:admin) { User.create!(:admin => true, :first => "Clark", :last => "Kent")} let!(:avg_joe) { User.create!(:admin => false, :first => "Joe", :last => "Shmo")} subject { User.admin_names } ! it { should include "Clark Kent" } it { should_not include "Joe Shmo" } end let!()

×