RSpec 3.0: Under the Covers
Upcoming SlideShare
Loading in...5
×
 

RSpec 3.0: Under the Covers

on

  • 6,333 views

Slides for a code reading of RSpec 3.0, detailing how the RSpec team eliminated monkey patching.

Slides for a code reading of RSpec 3.0, detailing how the RSpec team eliminated monkey patching.

Statistics

Views

Total Views
6,333
Views on SlideShare
316
Embed Views
6,017

Actions

Likes
0
Downloads
3
Comments
0

3 Embeds 6,017

http://modocache.svbtle.com 6015
http://digg.com 1
http://www.slideee.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

RSpec 3.0: Under the Covers RSpec 3.0: Under the Covers Presentation Transcript

  • RSpec 3.0: Under the Covers Achieving“Zero Monkey-Patching Mode” Brian Gesiak March 13th, 2014 Research Student, The University of Tokyo @modocache
  • Today • Monkey patching • How does RSpec work? • The rspec executable • Loading spec files • Example groups: describe and context • RSpec 2.11: describe no longer added to every Object • Running examples (it blocks) • Expectations • RSpec 2.11: expect-based syntax removes need for adding should to every Object
  • Monkey Patching How to Do It and Why You Shouldn’t class Array def sum # Also defined in `activesupport`! inject { |sum, x| sum + x } end end ! expect([1, 2, 3].sum).to eq 6
  • Monkey Patching How to Do It and Why You Shouldn’t class Array def sum # Also defined in `activesupport`! inject { |sum, x| sum + x } end end ! expect([1, 2, 3].sum).to eq 6
  • Monkey Patching How to Do It and Why You Shouldn’t class Array def sum # Also defined in `activesupport`! inject { |sum, x| sum + x } end end ! expect([1, 2, 3].sum).to eq 6 Monkey patching can lead to cryptic errors
  • Monkey Patching Root Objects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • Monkey Patching Root Objects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • Monkey Patching Root Objects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • Monkey Patching Root Objects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • Monkey Patching Root Objects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • RSpec 3.0 Historically, RSpec has extensively used monkey patching to create its readable syntax, adding methods…to every object. ! In the last few 2.x releases, we’ve worked towards reducing the amount of monkey patching done by RSpec. Zero Monkey-Patching Mode Myron Marston, RSpec Core Member @myronmarston
  • RSpec 3.0 Achieving“Zero Monkey-Patching Mode” $ rspec meetup_spec.rb
  • The rspec Executable rspec-core/exe/rspec #!/usr/bin/env ruby ! require 'rspec/core' RSpec::Core::Runner.invoke
  • The rspec Executable rspec-core/exe/rspec #!/usr/bin/env ruby ! require 'rspec/core' RSpec::Core::Runner.invoke
  • Loading Spec Files class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • Loading Spec Files class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • Loading Spec Files class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • Loading Spec Files class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • Loading Spec Files class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • Loading Spec Files class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • Loading Spec Files RSpec::Core::Configuration.load_spec_files def load_spec_files files_to_run.uniq.each { |f| load f } @spec_files_loaded = true end
  • Loading Spec Files RSpec::Core::Configuration.load_spec_files def load_spec_files files_to_run.uniq.each { |f| load f } @spec_files_loaded = true end
  • Building Example Groups describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • Building Example Groups describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • Where Does describe Come From? RSpec::Core::DSL def self.expose_example_group_alias(name) example_group_aliases << name ! (class << RSpec; self; end). __send__(:define_method, name) # ... ! expose_example_group_alias_globally(name) if exposed_globally? # defines on Module end
  • Where Does describe Come From? RSpec::Core::DSL def self.expose_example_group_alias(name) example_group_aliases << name ! (class << RSpec; self; end). __send__(:define_method, name) # ... ! expose_example_group_alias_globally(name) if exposed_globally? # defines on Module end
  • Where Does describe Come From? RSpec::Core::DSL def self.expose_example_group_alias(name) example_group_aliases << name ! (class << RSpec; self; end). __send__(:define_method, name) # ... ! expose_example_group_alias_globally(name) if exposed_globally? # defines on Module end
  • Where Does describe Come From? RSpec::Core::DSL def self.expose_example_group_alias(name) example_group_aliases << name ! (class << RSpec; self; end). __send__(:define_method, name) # ... ! expose_example_group_alias_globally(name) if exposed_globally? # defines on Module end
  • Disabling Module Monkey Patching RSpec.configure do |config| config.expose_dsl_globally = false end ! RSpec.describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do # ... end end Only Top Level describe Blocks Are Affected
  • Disabling Module Monkey Patching RSpec.configure do |config| config.expose_dsl_globally = false end ! RSpec.describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do # ... end end Only Top Level describe Blocks Are Affected
  • Disabling Module Monkey Patching RSpec.configure do |config| config.expose_dsl_globally = false end ! RSpec.describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do # ... end end Only Top Level describe Blocks Are Affected
  • Disabling Module Monkey Patching RSpec.configure do |config| config.expose_dsl_globally = false end ! RSpec.describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do # ... end end Only Top Level describe Blocks Are Affected Opt-in as of RSpec 2.11
  • Building Example Groups describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • Building Example Groups describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • Building Example Groups describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • Building Example Groups describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • Building Example Groups Hierarchies of Example Groups and Examples class ExampleGroup def self.example_group(*args, &example_group_block) # ... child = subclass(self, args, &example_group_block) children << child child end ! def self.examples @examples ||= [] end # ... end
  • Building Example Groups Hierarchies of Example Groups and Examples class ExampleGroup def self.example_group(*args, &example_group_block) # ... child = subclass(self, args, &example_group_block) children << child child end ! def self.examples @examples ||= [] end # ... end
  • Building Example Groups Hierarchies of Example Groups and Examples class ExampleGroup def self.example_group(*args, &example_group_block) # ... child = subclass(self, args, &example_group_block) children << child child end ! def self.examples @examples ||= [] end # ... end
  • Building Example Groups Hierarchies of Example Groups and Examples class ExampleGroup def self.example_group(*args, &example_group_block) # ... child = subclass(self, args, &example_group_block) children << child child end ! def self.examples @examples ||= [] end # ... end
  • Building Example Groups Hierarchies of Example Groups and Examples
  • Running the Examples class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • Running the Examples class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • Running the Examples class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • Running the Examples RSpec::Core::Example.run def run(example_group_instance, reporter) # ... begin run_before_each @example_group_instance. instance_exec(self, &@example_block) rescue Exception => e set_exception(e) ensure run_after_each end end
  • Running the Examples RSpec::Core::Example.run def run(example_group_instance, reporter) # ... begin run_before_each @example_group_instance. instance_exec(self, &@example_block) rescue Exception => e set_exception(e) ensure run_after_each end end
  • Running the Examples RSpec::Core::Example.run def run(example_group_instance, reporter) # ... begin run_before_each @example_group_instance. instance_exec(self, &@example_block) rescue Exception => e set_exception(e) ensure run_after_each end end
  • Running the Examples RSpec::Core::Example.run def run(example_group_instance, reporter) # ... begin run_before_each @example_group_instance. instance_exec(self, &@example_block) rescue Exception => e set_exception(e) ensure run_after_each end end
  • Making Expectations it 'is a grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The Deprecated should Syntax
  • Making Expectations it 'is a grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The Deprecated should Syntax
  • How should is Monkey Patched class Configuration # ... def syntax=(values) if Array(values).include?(:expect) Expectations::Syntax.enable_expect else Expectations::Syntax.disable_expect end ! if Array(values).include?(:should) Expectations::Syntax.enable_should else Expectations::Syntax.disable_should end end end RSpec::Expectations::Configuration
  • How should is Monkey Patched class Configuration # ... def syntax=(values) if Array(values).include?(:expect) Expectations::Syntax.enable_expect else Expectations::Syntax.disable_expect end ! if Array(values).include?(:should) Expectations::Syntax.enable_should else Expectations::Syntax.disable_should end end end RSpec::Expectations::Configuration
  • How should is Monkey Patched class Configuration # ... def syntax=(values) if Array(values).include?(:expect) Expectations::Syntax.enable_expect else Expectations::Syntax.disable_expect end ! if Array(values).include?(:should) Expectations::Syntax.enable_should else Expectations::Syntax.disable_should end end end RSpec::Expectations::Configuration
  • How should is Monkey Patched class Configuration # ... def syntax=(values) if Array(values).include?(:expect) Expectations::Syntax.enable_expect else Expectations::Syntax.disable_expect end ! if Array(values).include?(:should) Expectations::Syntax.enable_should else Expectations::Syntax.disable_should end end end RSpec::Expectations::Configuration
  • How should is Monkey Patched def enable_should(syntax_host=::Object.ancestors.last) # ... syntax_host.module_exec do def should(matcher=nil, message=nil, &block) # ... end ! def should_not(matcher=nil, message=nil, &block) # ... end end end RSpec::Expectations::Syntax
  • How should is Monkey Patched def enable_should(syntax_host=::Object.ancestors.last) # ... syntax_host.module_exec do def should(matcher=nil, message=nil, &block) # ... end ! def should_not(matcher=nil, message=nil, &block) # ... end end end RSpec::Expectations::Syntax
  • How should is Monkey Patched def enable_should(syntax_host=::Object.ancestors.last) # ... syntax_host.module_exec do def should(matcher=nil, message=nil, &block) # ... end ! def should_not(matcher=nil, message=nil, &block) # ... end end end RSpec::Expectations::Syntax
  • Making Expectations it 'is a grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The New expect(…).to Syntax
  • Making Expectations it 'is a grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The New expect(…).to Syntax
  • Making Expectations it 'is a grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The New expect(…).to Syntax
  • How expect is Implemented def enable_expect(syntax_host=::RSpec::Matchers) # ... syntax_host.module_exec do def expect(*target, &target_block) # ... end end end RSpec::Expectations::Syntax
  • How expect is Implemented def enable_expect(syntax_host=::RSpec::Matchers) # ... syntax_host.module_exec do def expect(*target, &target_block) # ... end end end RSpec::Expectations::Syntax
  • How expect is Implemented def enable_expect(syntax_host=::RSpec::Matchers) # ... syntax_host.module_exec do def expect(*target, &target_block) # ... end end end RSpec::Expectations::Syntax
  • Disabling should Monkey Patching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end
  • Disabling should Monkey Patching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end
  • Disabling should Monkey Patching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end
  • Disabling should Monkey Patching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end
  • Disabling should Monkey Patching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end expect(meetup).to be_hoppin
  • Disabling should Monkey Patching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end expect(meetup).to be_hoppin should Emits Deprecation Warning as of RSpec 3.0
  • Bringing it All Together class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • Bringing it All Together class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • Bringing it All Together class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • Bringing it All Together class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • Bringing it All Together class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • Want to Learn More about RSpec? • http://modocache.io/rspec-under-the-covers • Expectations in RSpec 3.0 • RSpec Output Formatting • Shared Examples in RSpec ! • Follow me on Twitter and GitHub at @modocache • First person to tweet me gets an Atom invite! #swag ! • Myron Marston’s blog: http://myronmars.to/n/dev-blog