Manchester
                                                          October 2009

        Uses & Abuses
      of Mocks & ...
Presentation goals
To establish a common language about
mock objects
To show some situations where mocks are
useful
To sho...
Presentation non-goals

The slides do not attempt…
 to teach you RSpec
 to cover OO design
 to use real-world code
So plea...
Basics
Starting Point

A mock is some sort of trivial object that
can be injected into code to make writing
specs easier
A mock c...
Traditional Definitions
Gerard Meszaros defines the following
test object roles, among others:
 dummy
 stub
 fake
 mock
Thes...
RSpec Definitions
RSpec provides three (equivalent) factory
methods for creating mock objects

  mock("my mock object")
  s...
My definitions
A mock is any trivial object injected into
your code to help describe its behaviour
A stub method is a metho...
RSpec concepts
describe "A behaviour description" do
  it "has an example" do
    my_mock = mock(Object,
                 ...
Uses
Defining an Interface
Ruby is 99% about objects and interfaces,
and 1% about classes
 describe Person do
   before(:each) d...
Defining an Interface
The set of options for message
expectations is too large to go into now,
but is extremely flexible
des...
Observable Behaviour
Sometimes you know how your code
should behave even if you ignore the
details of how it works
describ...
Observable Behaviour
This is actually an appropriate situation
for a custom matcher to deliberately
weaken our expectation...
Dependency Injection
The ability to redefine methods in Ruby
massively reduces the impact of depending on a
named class com...
Dependency Injection
 A stub can be used to override factory methods
describe DatabaseLogger do
  before(:each) do
    @co...
Simulating Situations
    Mocks let us put our code through situations
    that may be hard to recreate for real
describe ...
Degrees of Freedom
        describe Shape do
          before(:each) do
            @triangle = Shape.new(3)
          end...
Degrees of Freedom
         class Shape
           def initialize(sides)

           end

           def sides
           ...
Degrees of Freedom
We need t wo points to prove that the
implementation isn't simplistic

    describe Shape do
      befo...
Degrees of Freedom
We now have to solve the problem fully,
but at the expense of more spec code
          class Shape
    ...
Degrees of Freedom
We can reduce this to one using a mock
because we can't generate something that
is == the mock from in ...
Removing Duplication
Delegation introduces duplication
Consider the following class:


     class Room
       def open_doo...
Removing Duplication
The spec for this code is fairly complex

describe Room do
  before(:each) do
    @room = Room.new
  ...
Removing Duplication
Imagine a decorator object that delegates
to it
         class Bedsit
           def initialize(room)...
Removing Duplication
How do you write the spec?

describe Bedsit do
  before(:each) do
    @bedsit = Bedsit.new(Room.new)
...
Removing Duplication
  We actually only care about the delegation
describe Bedsit do
  before(:each) do
    @open_door_res...
Hiding Random Behaviour
 The following code may be useful to
 randomise collections of data

     class Randomiser
       ...
Hiding Random Behaviour
 This is the client

  class Sampler
    def initialize(values, logger)
      @values, @logger = v...
Hiding Random Behaviour
describe Sampler do
  before(:each) do
    @first = mock("first")
    @randomiser = mock(Randomise...
Exploratory Coding
Following the outside in BDD principle leads
you to write client code before provider
code, eg:
 A web ...
Abuses
Modifying the Subject
Modifying the subject of a behaviour
description is dangerous, you can:
 Introduce false positives
 ...
Modifying the Subject
This is a stable - albeit incomplete - spec

 describe Person do
   before(:each) do
     @person = ...
Modifying the Subject
…and one of the possible implementations
       class Person
         def initialize(first, last)
  ...
Modifying the Subject
This spec makes assumptions about the
subject

 describe Person do
   before(:each) do
     @person ...
Modifying the Subject
This code silently breaks the behaviour
       class Person
         def initialize(first, last)
   ...
Mock Mania
  describe ManicObject do
    before(:each) do
      @db = mock(Database)
      @logger = mock(Logger)
      @r...
Mock Trainwrecks
Stub methods on non-Class objects should
not (usually) return mocks that you
interact with
This indicates...
Mock Trainwrecks
The code that passes this spec must be
breaking encapsulation to expose its
internals
  describe Train do...
Mock Trainwrecks
 attr_reader shows we're off to a bad start
class Train                   class Cabin
  def initialize(en...
Mock Trainwrecks
 Let's break this down into single-step
 interactions
describe Train do
  before(:each) do
    @engine = ...
Mock Trainwrecks
The interface to engines is now not
exposed to clients of trains

         class Train
           def ini...
Mock Trainwrecks
Apply the pattern recursively…

describe Engine do
  before(:each) do
    @cabin = mock(Cabin)
    @engin...
Mock Trainwrecks
The cabin interface is now hidden

          class Engine
            def initialize(cabin)
             ...
Mock Trainwrecks
Apply again…

 describe Cabin do
   before(:each) do
     @door = mock(Door)
     @cabin = Cabin.new(@doo...
Mock Trainwrecks
The door interface is now hidden

          class Cabin
            def initialize(door)
              @d...
Mock Trainwrecks
Finally we prove that the door actually
locks

   describe Door do
     it "locks" do
       Door.new.loc...
Mock Trainwrecks
We've hidden the structure of our object
model, which satisfies the Law of Demeter
But we've now described...
Mock Trainwrecks
We could eliminate the use of mocks from
most (or all, if we wanted) of the spec
     describe Train do
 ...
Mock Trainwrecks
 On the subject of trainwrecks

describe "thetrainline.com" do
  it "should be loved by all" do
    URI.p...
Summary
Benefits of Mocks

Mocks let you explore and drive out new
interfaces needing a full implementation
Mocks let you implement...
Trouble from Mocks
Mocks make your specs interface-driven
not behaviour-driven
 Fine if using the interface is the
 behavi...
References

The RSpec Book - David Chelimsky et al
xUnit Patterns - Gerard Meszaros
Mocks Aren't Stubs - Martin Fowler
Moc...
Contact
Slides prepared by Ashley Moran
 ashley.moran@patchspace.co.uk
 www.patchspace.co.uk
I'm available for training/co...
Licence


Creative Commons licensed - contact me
for commercial use
Upcoming SlideShare
Loading in …5
×

Uses & Abuses of Mocks & Stubs

7,436 views

Published on

Published in: Technology
1 Comment
8 Likes
Statistics
Notes
  • Glad you liked this. This is really old now, maybe it's time I did something new about mock-driven development!
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
7,436
On SlideShare
0
From Embeds
0
Number of Embeds
36
Actions
Shares
0
Downloads
85
Comments
1
Likes
8
Embeds 0
No embeds

No notes for slide
  • Uses & Abuses of Mocks & Stubs

    1. 1. Manchester October 2009 Uses & Abuses of Mocks & Stubs With examples in Ruby using RSpec Ashley Moran Creative Commons Attribution-Noncommercial-Share Alike 2.0 UK: England & Wales License PatchSpace Ltd
    2. 2. Presentation goals To establish a common language about mock objects To show some situations where mocks are useful To show some situations where mocks are inappropriate, and problems they can cause
    3. 3. Presentation non-goals The slides do not attempt… to teach you RSpec to cover OO design to use real-world code So please ask questions if I've glossed over anything you think I should have explained
    4. 4. Basics
    5. 5. Starting Point A mock is some sort of trivial object that can be injected into code to make writing specs easier A mock can be made to respond to messages
    6. 6. Traditional Definitions Gerard Meszaros defines the following test object roles, among others: dummy stub fake mock These are precise and significantly different from each other
    7. 7. RSpec Definitions RSpec provides three (equivalent) factory methods for creating mock objects mock("my mock object") stub("my stub object") double("my double object") The RSpec book uses "test double" as the primary term; I still prefer "mock"
    8. 8. My definitions A mock is any trivial object injected into your code to help describe its behaviour A stub method is a method that returns a canned response This may be on a "real" object, more later A message expectation is an expectation that fails if a stub method is not called appropriately in spec code
    9. 9. RSpec concepts describe "A behaviour description" do it "has an example" do my_mock = mock(Object, :stub_method => "expected value") my_mock.stub_method.should == "expected value" end it "has an example with a message expectation" do my_mock = mock(Object, :expected_method => "value") my_mock.should_receive(:expected_method) my_mock.expected_method end end
    10. 10. Uses
    11. 11. Defining an Interface Ruby is 99% about objects and interfaces, and 1% about classes describe Person do before(:each) do @service = mock("service") @person = Person.new("Fred", "Flintstone") end it "can save itself" do @service.should_receive(:query).with( "STORE 'Fred', 'Flintstone';" ) @person.save(@service) end end
    12. 12. Defining an Interface The set of options for message expectations is too large to go into now, but is extremely flexible describe Person do before(:each) do @service = mock("service") @person = Person.new("Fred", "Flintstone") end it "sends relevant details" do @service.should_receive(:confirm_family).with( hash_including(:name => "Flintstone") ) @person.register(@service) end end
    13. 13. Observable Behaviour Sometimes you know how your code should behave even if you ignore the details of how it works describe XmlDoc do before(:each) do body = mock("body", :to_xml => "<body />") @doc = XmlDoc.new(body) end it "renders the document and body" do @doc.to_xml.should == "<doc><body /></doc>" end end
    14. 14. Observable Behaviour This is actually an appropriate situation for a custom matcher to deliberately weaken our expectation of the behaviour describe XmlDoc do # ... it "renders the document and body" do @doc.to_xml.should be_equivalent_xml_to( "<doc><body /></doc>" ) end end I wrote a matcher for this the other day, but that's another talk
    15. 15. Dependency Injection The ability to redefine methods in Ruby massively reduces the impact of depending on a named class compared to eg Java class DatabaseLogger def initialize(connection_details) @connection_details = connection_details end def log(message) Database.new(@connection_details).log(message) end end
    16. 16. Dependency Injection A stub can be used to override factory methods describe DatabaseLogger do before(:each) do @connection_details = mock("connection details") @database = mock(Database, :log => nil) @database_logger = DatabaseLogger.new(@connection_details) end it "creates a new Database" do Database.should_receive(:new). with(@connection_details). and_return(@database) @database_logger.log("take note") end end
    17. 17. Simulating Situations Mocks let us put our code through situations that may be hard to recreate for real describe ObjectSaver do before(:each) do @storage = mock("storage") @object_saver = ObjectSaver.new(@storage) end it "should save" do @storage.should_receive(:save).and_raise(OutOfStorageError) @object_saver.save(nil) end end Especially useful if there's a large number of situations (true of some stdlib code)
    18. 18. Degrees of Freedom describe Shape do before(:each) do @triangle = Shape.new(3) end it "knows how many sides it has" do @triangle.sides.should == 3 end end This Shape class is one dimensional Complaints about the examples at the end please Only one thing can vary - will one example do?
    19. 19. Degrees of Freedom class Shape def initialize(sides) end def sides 3 end end No - one example is not enough
    20. 20. Degrees of Freedom We need t wo points to prove that the implementation isn't simplistic describe Shape do before(:each) do @triangle = Shape.new(3) @square = Shape.new(4) end it "knows how many sides it has" do @triangle.sides.should == 3 @square.sides.should == 4 end end
    21. 21. Degrees of Freedom We now have to solve the problem fully, but at the expense of more spec code class Shape def initialize(sides) @sides = sides end def sides @sides end end This is a trivial example - don't write getters or use attr_reader in real code!
    22. 22. Degrees of Freedom We can reduce this to one using a mock because we can't generate something that is == the mock from in the code describe Shape do before(:each) do @triangle = Shape.new(@sides = mock("sides")) end it "knows how many sides it has" do @triangle.sides.should == @sides end end This technique is not always appropriate - a getter method is simply the easiest example
    23. 23. Removing Duplication Delegation introduces duplication Consider the following class: class Room def open_door(key) key == "sesame" ? :open : :closed end end
    24. 24. Removing Duplication The spec for this code is fairly complex describe Room do before(:each) do @room = Room.new end it "opens with the right key" do @room.open_door("sesame").should == :open end it "won't open with the wrong key" do @room.open_door("now!!!").should == :closed end end
    25. 25. Removing Duplication Imagine a decorator object that delegates to it class Bedsit def initialize(room) @room = room end def open_door(key) @room.open_door(key) end def fix_roof "it's fixed" end end
    26. 26. Removing Duplication How do you write the spec? describe Bedsit do before(:each) do @bedsit = Bedsit.new(Room.new) end it "opens with the right key" do # WARNING - duplication! @bedsit.open_door("sesame").should == :open end it "won't open with the wrong key" do # ... end end
    27. 27. Removing Duplication We actually only care about the delegation describe Bedsit do before(:each) do @open_door_response = mock("open_door_response") @key = mock("key") @room = mock(Room, :open_door => @open_door_response) @bedsit = Bedsit.new(@room) end it "delegates #open_door" do @room.should_receive(:open_door).with(@key) @bedsit.open_door(@key) end it "returns the delegated response" do @bedsit.open_door(nil).should == @open_door_response end end
    28. 28. Hiding Random Behaviour The following code may be useful to randomise collections of data class Randomiser def initialize(collection) @collection = collection end def to_a @collection.sort { rand <=> 0.5 } end end
    29. 29. Hiding Random Behaviour This is the client class Sampler def initialize(values, logger) @values, @logger = values, logger end def sample @logger.log(Randomiser.new(@values).to_a[0]) end end
    30. 30. Hiding Random Behaviour describe Sampler do before(:each) do @first = mock("first") @randomiser = mock(Randomiser, :to_a => [@first, 2, 3]) Randomiser.stub(:new => @randomiser) @logger = mock("logger", :log => nil) @values = mock("values") @sampler = Sampler.new(@values, @logger) end it "makes a new Randomiser" do Randomiser.should_receive(:new).with(@values) @sampler.sample end it "logs a sample of the data" do @logger.should_receive(:log).with(@first) @sampler.sample end end
    31. 31. Exploratory Coding Following the outside in BDD principle leads you to write client code before provider code, eg: A web controller before a domain model A domain model before a storage system Data processing before file system access I'm going to defer this until the "Mock Trainwreck" abuse later
    32. 32. Abuses
    33. 33. Modifying the Subject Modifying the subject of a behaviour description is dangerous, you can: Introduce false positives Make the specs brittle What I call the "subject" is sometimes known as Object Under Test, Subject Under Test or System Under Test
    34. 34. Modifying the Subject This is a stable - albeit incomplete - spec describe Person do before(:each) do @person = Person.new("Fred", "Flintstone") end it "should have a full name" do @person.name.should == "Fred FLINTSTONE" end end
    35. 35. Modifying the Subject …and one of the possible implementations class Person def initialize(first, last) @first, @last = first, last end def name "#{@first} #{last}" end private def last @last.upcase end end
    36. 36. Modifying the Subject This spec makes assumptions about the subject describe Person do before(:each) do @person = Person.new("Fred", "Flintstone") @person.stub(:last => "FLINTSTONE") end it "should have a full name" do @person.name.should == "Fred FLINTSTONE" end end
    37. 37. Modifying the Subject This code silently breaks the behaviour class Person def initialize(first, last) @first, @last = first, last end def name "#{@first} #{last}" end private def last @last.downcase end end False positives are a catastrophe!
    38. 38. Mock Mania describe ManicObject do before(:each) do @db = mock(Database) @logger = mock(Logger) @rss_feed = mock(URI) @kitchen_sink = mock(KitchenSink) @cleaning_solution = mock(FairyLiquid) end # ... end Specs like this indicate a Single Responsibility violation. The ManicObject class is probably highly unstable
    39. 39. Mock Trainwrecks Stub methods on non-Class objects should not (usually) return mocks that you interact with This indicates a violation of Law of Demeter in the code Mock trainwrecks tie your specs to the current code structure, so are brittle and scatter the specs for the other objects
    40. 40. Mock Trainwrecks The code that passes this spec must be breaking encapsulation to expose its internals describe Train do before(:each) do @door = mock(Door) @cabin = mock(Cabin, :door => @door) @engine = mock(Engine, :cabin => @cabin) @train = Train.new(@engine) end it "can lock its door" do @door.should_receive(:lock).once @train.lock_door end end
    41. 41. Mock Trainwrecks attr_reader shows we're off to a bad start class Train class Cabin def initialize(engine) attr_reader :door @engine = engine end def initialize(door) @door = door def lock_door end @engine.cabin.door.lock end end end class Engine class Door attr_reader :cabin def lock "it's locked" def initialize(cabin) end @cabin = cabin end end end
    42. 42. Mock Trainwrecks Let's break this down into single-step interactions describe Train do before(:each) do @engine = mock(Engine) @train = Train.new(@engine) end it "locks its cabin door" do @engine.should_receive(:lock_door).with(no_args) @train.lock_door end end
    43. 43. Mock Trainwrecks The interface to engines is now not exposed to clients of trains class Train def initialize(engine) @engine = engine end def lock_door @engine.lock_door end end
    44. 44. Mock Trainwrecks Apply the pattern recursively… describe Engine do before(:each) do @cabin = mock(Cabin) @engine = Engine.new(@cabin) end it "locks its cabin door" do @cabin.should_receive(:lock_door).with(no_args) @engine.lock_door end end
    45. 45. Mock Trainwrecks The cabin interface is now hidden class Engine def initialize(cabin) @cabin = cabin end def lock_door @cabin.lock_door end end
    46. 46. Mock Trainwrecks Apply again… describe Cabin do before(:each) do @door = mock(Door) @cabin = Cabin.new(@door) end it "locks its cabin door" do @door.should_receive(:lock).with(no_args) @cabin.lock_door end end
    47. 47. Mock Trainwrecks The door interface is now hidden class Cabin def initialize(door) @door = door end def lock_door @door.lock end end
    48. 48. Mock Trainwrecks Finally we prove that the door actually locks describe Door do it "locks" do Door.new.lock.should == "it's locked" end end class Door def lock "it's locked" end end
    49. 49. Mock Trainwrecks We've hidden the structure of our object model, which satisfies the Law of Demeter But we've now described three internal interfaces (on Engine, Cabin and Door) The code can be broken if any internal interface changes In an unstable object model, this can lead to a lot of rework
    50. 50. Mock Trainwrecks We could eliminate the use of mocks from most (or all, if we wanted) of the spec describe Train do before(:each) do @door = mock(Door) @cabin = Cabin.new(@door) @engine = Engine.new(@cabin) @train = Train.new(@engine) end it "can lock its door" do @door.should_receive(:lock).once @train.lock_door end end Be warned that this example is contrived, and strays far into OO design territory
    51. 51. Mock Trainwrecks On the subject of trainwrecks describe "thetrainline.com" do it "should be loved by all" do URI.parse("thetrainline.com").should_not be_shit end end Unfortunately, Ruby (as of 1.9.1) does not define URI#shit? so a custom matcher would be required
    52. 52. Summary
    53. 53. Benefits of Mocks Mocks let you explore and drive out new interfaces needing a full implementation Mocks let you implement your code incrementally and get to green faster Mocks let you prove properties of your code to the degree of precision and strength you demand
    54. 54. Trouble from Mocks Mocks make your specs interface-driven not behaviour-driven Fine if using the interface is the behaviour Can introduce overhead and rework if the interfaces are numerous and trival Better to throw away many of these Mocked specs are more brittle
    55. 55. References The RSpec Book - David Chelimsky et al xUnit Patterns - Gerard Meszaros Mocks Aren't Stubs - Martin Fowler Mock Roles, not Objects - Steve Freeman et al
    56. 56. Contact Slides prepared by Ashley Moran ashley.moran@patchspace.co.uk www.patchspace.co.uk I'm available for training/consultancy in Ruby and RSpec on a freelance basis I code for food
    57. 57. Licence Creative Commons licensed - contact me for commercial use

    ×