RSpec 3.0: Under the Covers

67 %
33 %
Information about RSpec 3.0: Under the Covers
Technology

Published on March 12, 2014

Author: bgesiak

Source: slideshare.net

Description

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

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

#swag presentations

Add a comment

Related presentations

Related pages

More on RSpec - RSpec 3.0: Under the Covers

RSpec 3.0: Under the Covers from Brian Gesiak More on RSpec Code Reading: RSpec 3.0 Expectations Code Reading: Rspec 3.0 Shared Examples Code Reading ...
Read more

RSpec: Behaviour Driven Development for Ruby

The RSpec Book will introduce ... In addition to covering the technical aspects of using RSpec, it also covers best practices for using them so you get ...
Read more

Code Reading: Expectations in RSpec 3.0 - ⌘U on Svbtle

Code Reading: Expectations in RSpec 3.0. If you’ve downloaded the RSpec 3.0 beta, ... RSpec 3.0: Under the Covers; Code Reading: Rspec 3.0 Shared Examples;
Read more

Module: RSpec::Matchers — Documentation by YARD 0.8.7.4

RSpec::Matchers provides a number of useful matchers we use to define ... RSpec will also create custom matchers for predicates ... Under the covers, ...
Read more

GitHub - rspec/rspec-core: RSpec runner and formatters

rspec-core - RSpec runner ... 2-99-maintenance 3-0-maintenance 3-1-maintenance 3-2 ... Under the covers ...
Read more

Code Reading: Message Stubbing in RSpec - ⌘U

Code Reading: Message Stubbing in RSpec. RSpec isn’t just nested context blocks and expect ... As discussed in my slides for “RSpec 3.0: Under the ...
Read more

`cover` matcher - Built in matchers - RSpec Expectations ...

Project: RSpec Expectations 3.0 Publisher: RSpec. Change version 3.4; 3.3; 3.2; 3.1; 3.0; 2.99; 2.14; ... Use the cover matcher to specify that a range ...
Read more

Make `any_instance` opt-in for RSpec 3.0 · Issue #336 ...

Make `any_instance` opt-in for RSpec 3.0 #336. ... to get legacy code under test but I don't ... too many scenarios to try to cover to make it ...
Read more