Straight Up RSpec  a neat Ruby BDD tool
The MenuOrganization                 $0Options                      $0Refactoring Exercise         $0Configuration         ...
Organization
.rspeclib/barkeep.rblib/barkeep/martini.rbspec/spec_helper.rbspec/support/configure.rbspec/support/matchers/drink.rbspec/m...
spec_helperrequire rspecrequire barkeepDir["#{File.dirname(__FILE__)}/support/**/*.rb"].  each {|file| require file }     ...
Options
$ rspec spec -f documentation
$ rspec spec/martini_spec.rb -l 16
$ rspec spec -t slow
Barkeepgithub.com/gsterndale/barkeep   a refactoring exercise
Martini Spec Draftdescribe "Barkeep::Martini" do  before do    @martini = Barkeep::Martini.new  end  it "should have an at...
Custom Matchermodule AttributeMatchers  Spec::Matchers.define :have_attribute do |name|    match do |target|      getter =...
Custom Matcherdescribe "Barkeep::Martini" do  include AttributeMatchers  before do    @martini = Barkeep::Martini.new  end...
subject()describe "Barkeep::Martini" do  include AttributeMatchers  subject { Barkeep::Martini.new }  it "should have an a...
Implicit subject()describe "Barkeep::Martini" do  include AttributeMatchers  subject { Barkeep::Martini.new }  it "should ...
DRY Messagesdescribe "Barkeep::Martini" do  include AttributeMatchers  subject { Barkeep::Martini.new }  it { should have_...
Derived subject()describe Barkeep::Martini do  include AttributeMatchers  it { should have_attribute(:booze) }  it { shoul...
config.include()RSpec.configure do |config|    config.include AttributeMatchersend                                  support...
config.include()describe Barkeep::Martini do  it { should have_attribute(:booze) }  it { should have_attribute(:garnish) }e...
Original Draftdescribe "Barkeep::Martini" do  before do    @martini = Barkeep::Martini.new  end  it "should have an attrib...
Whiskey Specdescribe Barkeep::Whiskey do  it { should have_attribute(:booze) }  it { should have_attribute(:glass) }end   ...
Martini Specdescribe Barkeep::Martini do  it { should have_attribute(:booze) }  it { should have_attribute(:glass) }  it {...
Shared Example Groupshare_examples_for "a drink" do  it { should have_attribute(:booze) }  it { should have_attribute(:gla...
Whiskey Specdescribe Barkeep::Whiskey do  it_should_behave_like "a drink"end                                    whiskey_sp...
Martini Specdescribe Barkeep::Martini do  it_should_behave_like "a drink"  it { should have_attribute(:garnish) }end      ...
Whiskey & Martini #ingredientsshare_examples_for "a drink" do  it { should have_attribute(:booze) }  it { should have_attr...
context()describe Barkeep::Martini do  it_should_behave_like "a drink"  it { should have_attribute(:garnish) }  it { shoul...
let()describe Barkeep::Martini do  it_should_behave_like "a drink"  it { should have_attribute(:garnish) }  it { should ha...
its()describe Barkeep::Martini do  it_should_behave_like "a drink"  it { should have_attribute(:garnish) }  it { should ha...
its()describe Barkeep::Martini do  it_should_behave_like "a drink"  it { should have_attribute(:garnish) }  it { should ha...
have()describe Barkeep::Martini, ".new with mixer" do  let(:mixer) { vermouth }  subject { Barkeep::Martini.new(:mixer => ...
have()describe Barkeep::Martini, ".new with mixer" do  let(:mixer) { vermouth }  subject { Barkeep::Martini.new(:mixer => ...
expect()describe Barkeep::Martini do  let(:mixer) { vermouth }  it "should include mixer in ingredients" do    expect { su...
stub()describe Barkeep::Terminal, "#printer" do  let(:ones) { "1.1.1.1" }  let(:epson) { double("Espon 5000") }  subject {...
should_receive()describe Barkeep::Terminal, "#printer" do  let(:ones) { "1.1.1.1" }  let(:epson) { double("Espon 5000") } ...
Configuration
RSpec.configureRSpec.configure do |config|  config.include MyMartiniMatchers  config.include ModelHelpers, :type => :modele...
RSpec.configureRSpec.configure do |config|  config.before(:all) {} #    run   once  config.before(:each) {} #   run   befor...
InclusionRSpec.configure do |c|  c.filter_run :focus => true  c.run_all_when_everything_filtered = trueend# in any spec fi...
ExclusionRSpec.configure do |c|  c.exclusion_filter = { :ruby => lambda {|version|     !(RUBY_VERSION.to_s =~ /^#{version....
Other Libraries
RSpec::Rails
response.should redirect_to("some/url")response.should be_successresponse.should have_tag("div", "some text")
Shoulda::Matchers
describe User do  it { should belong_to(:account) }  it { should have_many(:posts) }  it { should validate_presence_of(:em...
CHEERS
let() gotchadescribe User, ".admin_names" do  let(:admin) { User.create!(:admin => true,    :first => "Clark",    :last =>...
let() work-arounddescribe User, ".admin_names" do  let(:admin) { User.create!(:admin => true,    :first => "Clark",    :la...
let!()describe User, ".admin_names" do  let!(:admin) { User.create!(:admin => true,    :first => "Clark",    :last => "Ken...
Upcoming SlideShare
Loading in …5
×

Straight Up RSpec

7,740 views

Published on

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

Published in: Technology, Self Improvement
1 Comment
24 Likes
Statistics
Notes
No Downloads
Views
Total views
7,740
On SlideShare
0
From Embeds
0
Number of Embeds
239
Actions
Shares
0
Downloads
111
Comments
1
Likes
24
Embeds 0
No embeds

No notes for slide
  • \n
  • going beyond the basics\noffering some opinions\nhighlighting some (relatively) new hotness\n
  • \n
  • \n
  • partial simple project directory structure\n
  • Keep spec_helper slim.\n\nInclude it in all spec files.\n\nEspecially in a Rails project. You’ll want to regenerate it when you update RSpec.\n\nMove Helpers into spec/support/. I even move configuration there (configure.rb).\n
  • Command line options that can be specified in .rspec when using the RSpec Rake task.\n
  • Output format\n
  • Line number of a context/describe block or example\n
  • tags\n
  • An exercise in refactoring some specs\n
  • Original spec\n
  • This example uses the RSpec matcher DSL.\n\nMatchers can be created from scratch as well. To do so you need:\nA module with a method (e.g. have_attribute()) that instantiates a class\nThe class must define matches?(target), description(), failure_message_for_should(), failure_message_for_should_not()\n
  • Using our new custom matcher, have_attribute()\n
  • \n
  • implicit subject()\n\ncall “should” off “nothing”\n
  • Without redundant example (it) messages\n
  • Derived subject().\n\nUse Class, not String as argument to describe()\n
  • \n
  • If AttributeMatchers are included in Spec::Runner configuration, we don’t need to include it here\n
  • Original spec\n
  • Hmmm... Looks similar to martini_spec\n
  • Now both Whiskey and Martini have booze and glass attributes.\n\nLots of overlap.\n
  • Note that there are NO INSTANCE VARIABLES!\n\nWe use the subject() here.\n
  • The shared examples with use the derived subject().\n
  • \n
  • \n
  • Also note the “include” matcher!\n
  • There is a gotcha! More if we have time.\n
  • \n
  • OR\n\nlose the context() and break the example into its own describe() block\n\nNote the use of the “.” before the class method name (“new”). This is a common convention. “#” is used for instance methods\n
  • “items” is just sugar, we could use whatever word we want here e.g. “elements”\n
  • OR\n\na more readable example\n
  • I would not advocate this usage of expect().\nOther uses:\nexpect{}.to change{}.by(1)\nexpect{}.to raise_error\n\n
  • Any call to the .new() method on the Printer class will return epson.\n\nNote that epson is a test double(). It’s dumb. It doesn’t know how to do (or respond to) the real Printer methods.\n
  • Other test double libraries have test spies.\n
  • Configuring RSpec\n
  • Helpers included in RSpec configuration are included for all specs if no :type is specified.\n
  • BEWARE before(:all)!!! Not just in RSpec.configure, but in all specs.\n \n Who’s been burned by before(:all)?\n \n Improper use can lead to the unit test anti-pattern called “Interacting Tests” (or “Generous Leftovers”) and therefor erratic and hard to resolve failures.\n
  • Every example and example group comes with metadata information\n\nWhen you run the $ rspec command, only the examples that have :focus => true tag in the hash.\n\nIf there are no examples tagged with “:focus => true”, all examples will be run. \n
  • Do NOT run examples that are tagged with a different Ruby version when running the “rspec” command.\n
  • \n
  • Integrated fixture loading (and unloading).\nSpecial generators that generate RSpec code examples.\nHelp isolate your classes using example groups (e.g. Controller Examples don’t actually render Views)\nRails specific Matchers.\n
  • To name a few Matchers.\n
  • \n
  • Shoulda’s ActiveRecord Matchers.\n
  • \n
  • THIS WILL FAIL!\n
  • THIS WILL FAIL!\n
  • Green\n
  • Implementation possibility\n
  • Distributed (across cores, processors and even other machines) testing framework\n
  • A DRb test server implementation that forks itself every time your tests are run instead of unloading the RAILS constant.\n
  • Rspec::Matchers now offers you two approaches to differentiating between object identity.\n
  • Straight Up RSpec

    1. 1. Straight Up RSpec a neat Ruby BDD tool
    2. 2. The MenuOrganization $0Options $0Refactoring Exercise $0Configuration $0Other Libraries $0
    3. 3. Organization
    4. 4. .rspeclib/barkeep.rblib/barkeep/martini.rbspec/spec_helper.rbspec/support/configure.rbspec/support/matchers/drink.rbspec/martini_spec.rb
    5. 5. spec_helperrequire rspecrequire barkeepDir["#{File.dirname(__FILE__)}/support/**/*.rb"]. each {|file| require file } spec_helper.rb
    6. 6. Options
    7. 7. $ rspec spec -f documentation
    8. 8. $ rspec spec/martini_spec.rb -l 16
    9. 9. $ rspec spec -t slow
    10. 10. Barkeepgithub.com/gsterndale/barkeep a refactoring exercise
    11. 11. Martini Spec Draftdescribe "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=) endend martini_spec.rb
    12. 12. Custom Matchermodule 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 endend have_attribute.rb
    13. 13. Custom Matcherdescribe "Barkeep::Martini" do include AttributeMatchers before do @martini = Barkeep::Martini.new end it "should have an attribute named booze" do @martini.should have_attribute(:booze) end it "should have an attribute named garnish" do @martini.should have_attribute(:garnish) endend martini_spec.rb
    14. 14. subject()describe "Barkeep::Martini" do include AttributeMatchers subject { Barkeep::Martini.new } it "should have an attribute named booze" do subject.should have_attribute(:booze) end it "should have an attribute named garnish" do subject.should have_attribute(:garnish) endend martini_spec.rb
    15. 15. Implicit subject()describe "Barkeep::Martini" do include AttributeMatchers subject { Barkeep::Martini.new } it "should have an attribute named booze" do should have_attribute(:booze) end it "should have an attribute named garnish" do should have_attribute(:garnish) endend martini_spec.rb
    16. 16. DRY Messagesdescribe "Barkeep::Martini" do include AttributeMatchers subject { Barkeep::Martini.new } it { should have_attribute(:booze) } it { should have_attribute(:garnish) }end martini_spec.rb
    17. 17. Derived subject()describe Barkeep::Martini do include AttributeMatchers it { should have_attribute(:booze) } it { should have_attribute(:garnish) }end martini_spec.rb
    18. 18. config.include()RSpec.configure do |config| config.include AttributeMatchersend support/configure.rb
    19. 19. config.include()describe Barkeep::Martini do it { should have_attribute(:booze) } it { should have_attribute(:garnish) }end martini_spec.rb
    20. 20. Original Draftdescribe "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=) endend martini_spec.rb
    21. 21. Whiskey Specdescribe Barkeep::Whiskey do it { should have_attribute(:booze) } it { should have_attribute(:glass) }end whiskey_spec.rb
    22. 22. Martini Specdescribe Barkeep::Martini do it { should have_attribute(:booze) } it { should have_attribute(:glass) } it { should have_attribute(:garnish) }end martini_spec.rb
    23. 23. Shared Example Groupshare_examples_for "a drink" do it { should have_attribute(:booze) } it { should have_attribute(:glass) }end support/examples/drink.rb
    24. 24. Whiskey Specdescribe Barkeep::Whiskey do it_should_behave_like "a drink"end whiskey_spec.rb
    25. 25. Martini Specdescribe Barkeep::Martini do it_should_behave_like "a drink" it { should have_attribute(:garnish) }end martini_spec.rb
    26. 26. Whiskey & Martini #ingredientsshare_examples_for "a drink" do it { should have_attribute(:booze) } it { should have_attribute(:glass) } it "should have an ingredients Array" do subject.ingredients.should be_a Array endend support/examples/drink.rb
    27. 27. context()describe Barkeep::Martini do it_should_behave_like "a drink" it { should have_attribute(:garnish) } it { should have_attribute(:mixer) } context "with a mixer" do before do @mixer = vermouth subject.mixer = @mixer end it "should include mixer in ingredients" do subject.ingredients.should include @mixer end end martini_spec.rb
    28. 28. let()describe Barkeep::Martini do it_should_behave_like "a drink" it { should have_attribute(:garnish) } it { should have_attribute(:mixer) } context "with a mixer" do let(:mixer) { vermouth } before { subject.mixer = mixer } it "should include mixer in ingredients" do subject.ingredients.should include mixer end endend martini_spec.rb
    29. 29. its()describe Barkeep::Martini do it_should_behave_like "a drink" it { should have_attribute(:garnish) } it { should have_attribute(:mixer) } context "with a mixer" do let(:mixer) { vermouth } before { subject.mixer = mixer } its(:ingredients) { should include mixer } endend martini_spec.rb
    30. 30. its()describe Barkeep::Martini do it_should_behave_like "a drink" it { should have_attribute(:garnish) } it { should have_attribute(:mixer) }enddescribe Barkeep::Martini, ".new with mixer" do let(:mixer) { vermouth } subject { Barkeep::Martini.new(:mixer => mixer) } its(:ingredients) { should include mixer }end martini_spec.rb
    31. 31. have()describe Barkeep::Martini, ".new with mixer" do let(:mixer) { vermouth } subject { Barkeep::Martini.new(:mixer => mixer) } its(:ingredients) { should include mixer } its(:ingredients) { should have(1).items }end martini_spec.rb
    32. 32. have()describe Barkeep::Martini, ".new with mixer" do let(:mixer) { vermouth } subject { Barkeep::Martini.new(:mixer => mixer) } its(:ingredients) { should include mixer } it { should have(1).ingredients }end martini_spec.rb
    33. 33. expect()describe Barkeep::Martini do let(:mixer) { vermouth } it "should include mixer in ingredients" do expect { subject.mixer = mixer }. to change { subject.ingredients }. from( [] ). to [ mixer ] endend martini_spec.rb
    34. 34. stub()describe Barkeep::Terminal, "#printer" do let(:ones) { "1.1.1.1" } let(:epson) { double("Espon 5000") } subject { Barkeep::Terminal.new(:ip => ones) } before do Printer.stub(:new) { epson } end it "should have a printer" do subject.printer.should eq epson endend terminal_spec.rb
    35. 35. should_receive()describe Barkeep::Terminal, "#printer" do let(:ones) { "1.1.1.1" } let(:epson) { double("Espon 5000") } subject { Barkeep::Terminal.new(:ip => ones) } it "should have a printer" do Printer.should_receive(:new). with(:ip => ones) { epson } subject.printer endend terminal_spec.rb
    36. 36. Configuration
    37. 37. RSpec.configureRSpec.configure do |config| config.include MyMartiniMatchers config.include ModelHelpers, :type => :modelend# spec/model/martini_spec.rbdescribe Martini, :type => :model do …end support/configure.rb
    38. 38. RSpec.configureRSpec.configure do |config| config.before(:all) {} # run once config.before(:each) {} # run before each example config.after (:each) {} # run after each example config.after (:all) {} # run once # run before each example of type :model config.before(:each, :type => :model) {}end support/configure.rb
    39. 39. InclusionRSpec.configure do |c| c.filter_run :focus => true c.run_all_when_everything_filtered = trueend# in any spec filedescribe "something" do it "does something", :focus => true do # .... endend support/configure.rb
    40. 40. ExclusionRSpec.configure do |c| c.exclusion_filter = { :ruby => lambda {|version| !(RUBY_VERSION.to_s =~ /^#{version.to_s}/) }}end# in any spec filedescribe "something" do it "does something", :ruby => 1.8 do # .... end it "does something", :ruby => 1.9 do # .... support/configure.rb
    41. 41. Other Libraries
    42. 42. RSpec::Rails
    43. 43. response.should redirect_to("some/url")response.should be_successresponse.should have_tag("div", "some text")
    44. 44. Shoulda::Matchers
    45. 45. describe User do it { should belong_to(:account) } it { should have_many(:posts) } it { should validate_presence_of(:email) } it { should allow_value("f@b.com").for(:email) } it { should_not allow_value("test").for(:email) }end
    46. 46. CHEERS
    47. 47. let() gotchadescribe 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 spec/user_spec.rb
    48. 48. let() work-arounddescribe 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 spec/user_spec.rb
    49. 49. let!()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 spec/user_spec.rb

    ×