advertisement

Print

Behavior-Driven Development Using Ruby (Part 2)
Pages: 1, 2, 3, 4

We've got two different mock objects being created here, one for our user interface, and another for our grid. You can see that we set the interface of the game object using mock("UI"), which is a blank mock object created by RSpec. The following method fills in our expectations:



  def mock_player_selection
    @game.interface.should_receive(:get_players).and_return(["Gregory","Joe"])
  end

Here we're basically creating a simple stubbed out object, where we expect that some_interface.get_players will be called, and return an array with some names in it. We're using this to test how our game will behave upon startup, and also to test some simple logic:

  it "should have an array of players" do 
    @game.players.should == ["Gregory","Joe"]
  end             

  it "should set the first player entered via the UI to the current_player" do
    @game.current_player.should == "Gregory" 
  end

Our mock objects will self-verify, and they'll also help us by responding things we're expecting so that we can form examples around them. It's worth keeping in mind that a mock isn't going to automatically return meaningful data--you need to take care that what you're asking for is really what you expect from your dependent classes.

Let's take a quick look at the implementation of Game so far:

(13) lib/dots/game.rb
module Dots
  class Game
    attr_accessor :interface   
    attr_reader :players, :grid            

    def start
      @players = interface.get_players
      @turn = 0 
      rows,cols = interface.get_grid_size 
      @grid = Dots::Grid.new(rows,cols)
    end  

    def current_player
      @players[@turn]    
    end

  end      
end

You can see where interface.get_players is being called, and also where our grid interaction happens. If you look over the examples, it should be pretty clear what's going on here. There is one small trick we do in our examples that's worth revisiting, though.

Adding mock calls to existing classes

With our interface code, it's very clear what's happening so long as you're familiar with duck typing. We're passing in an object that acts like something else, and Ruby is happy to work with it. However, if you look at Game#start, you can see that our Grid objects are explicitly instantiated.

We'd like to avoid testing these objects directly, for a number of reasons. Perhaps the most important is that we should be able to focus on just the interactions with that object, and not its implementation details. Rather than using a technique such as dependency injection or some other design workaround to get at this code and insert our mock objects, RSpec offers us a shortcut:

  def mock_prompt_for_grid_size               
    grid = mock("grid")  
    @game.interface.should_receive(:get_grid_size).and_return([8,10])            
    Dots::Grid.should_receive(:new).with(8,10).and_return(grid)
  end

By stubbing out the constructor call, we can have it return a mock object, which we can then define our expectations on. This largely decouples us from the class without any additional work, and with some extra boilerplate, could actually completely separate us from its implementation.

As we evolve the game class, the interactions get more complex. To cope with this, we encapsulate our helper methods in a module, which can then be included in our example sets as needed.

The final set of mock helpers for the Game specs look like this:

(15) spec/game_spec.rb
module GameStateMocks

  def mock_player_selection
    @game.interface.should_receive(:get_players).and_return(["Gregory","Joe"])
  end

  def mock_prompt_for_grid_size               
    grid = mock("grid")  
    @game.interface.should_receive(:get_grid_size).and_return([8,10])            
    Dots::Grid.should_receive(:new).with(8,10).and_return(grid)
  end

  def mock_initial_game_state
    @game.interface = mock("UI") 
    mock_player_selection
    mock_prompt_for_grid_size
    @game.interface.should_receive(:update_display).with(an_instance_of(Dots::Game))
  end  

  def stub_move(edge,boxes_completed=0)  
    @game.interface = mock("UI")             
    box_set = mock("box set")
    if boxes_completed.zero?
      box_set.should_receive(:empty?).and_return(true)
    else
      box_set.should_receive(:empty?).and_return(false)
      box_set.should_receive(:size).and_return(boxes_completed)
    end
    @game.interface.should_receive(:get_move).and_return(edge)       
    @game.interface.should_receive(:update_display).with(an_instance_of(Dots::Game))
    @game.grid.should_receive(:connect).with(*edge).and_return(box_set)
    @game.move  
  end

end

As you can see here, we've defined a whole bunch of behaviors, some for objects that don't exist yet, others for objects that we only want to test our interactions with. The key here is to mock out all the interactions you need to run your specs standalone if needed, but no more. If your code doesn't absolutely depend on a certain behavior, there is no need to mock it out.

Although it might be tricky to get your mocks right, they make your specs and implementation code very simple and expressive. Take the following specs, for example:

describe "A Game in progress with two players" do

 include GameStateMocks

 before :each do
   @game = Dots::Game.new
    mock_initial_game_state  
   @game.start  
 end

 it "should alternate players when a box is not completed" do
   @game.current_player.should == "Gregory" 
   stub_move([[0,0],[0,1]],0)
   @game.current_player.should == "Joe"     
   stub_move([[0,1],[1,1]],0) 
   @game.current_player.should == "Gregory" 
 end     

 it "should not change players if 1 box is completed" do
   @game.current_player.should == "Gregory" 
   stub_move([[0,0],[0,1]],1)
   @game.current_player.should == "Gregory"      
 end     

 it "should not change players if 2 boxes are completed" do
   @game.current_player.should == "Gregory" 
   stub_move([[0,0],[0,1]],2)
   @game.current_player.should == "Gregory"      
 end

end

You can see that we're stubbing out moves on the grid, clearly expressing how turn order should progress. Though the actual interactions here are simple, imagine how handy a mock could be when dealing with real-world resources you can't meaningfully test live against, such as web services. As long as you can account for the interactions you expect (return values, error conditions, etc.), you can create mock objects to mimic the behaviors and fully test your code without direct ties to those resources.

Until Next Time…

I've kept my focus mostly on exposing the interesting test bits, but what you'll find in this package is a fully functional game, albiet a little simple and fragile. Because it's broken up by iteration, you can hopefully get a good feel for the workflow. I've even implemented a simple text-based UI using HighLine for the game, which I've not covered here. Interestingly enough, this didn't require me to make many modifications at all to the rest of the code, even though I built it without a UI hooked up.

The end result looked something like this:

0   1   2   3   4   5
+ - + - + - + - + - + 5
-   -   -   -   -   -
+ - + - + - + - + - + 4
-   -   -   -   -   -
+ - + - + - + - + - + 3
-   -   -   -   -   -
+ - + - + - + - + - + 2
-   -   -   -   -   -
+===+ - + - + - + - + 1
| G |   -   -   -   -
+===+===+ - + - + - + 0

Current Player: Greg
Scores: ( Greg: 1 Joe: 0)

Draw from (x,y):

You can find this in iteration 16, runnable via play_dots.rb. See the underlying code for more details.

We'll be back soon to wrap up this exploration of BDD by looking at some of the advanced tools and techniques that experienced users leverage to improve their process. For now, hopefully you've gained a decent understanding of how things work in practice, and will have fun playing Dots and Boxes.

Gregory Brown is a New Haven, CT based Rubyist who spends most of his time on free software projects in Ruby. He is the original author of Ruby Reports.


Return to Ruby.