David Chelimsky

random thoughtlessness

A case against a case against mocking and stubbing

In his blog entitled A case against Mocking and Stubbing, Brian Cardarella says

since I’ve been TATFT with TDD and some BDD I’ve come to believe that mocking/stubbing is a horrible idea and it can hurt the development process

Please take a minute to let that soak in. “a horrible idea” and “can hurt the development process”. In fact, please go read the post before you read on. I’d rather you read his words before you read my interpretation of them.

Back? Cool. Scary stuff, huh?

But never fear, because it’s not about you (unless you are Brian). What he is really saying is this:

since I’ve been TATFT with my own personal approach to testing Rails applications, which is a little bit different from what the TDD/BDD guys are doing and is largely based on Rails conventions which encourage you to couple layers together in your tests, I’ve come to believe that mocking and stubbing, two concepts that assist and encourage testing in isolation, which is the opposite of the kind of testing I like to do, is a horrible idea for me and can hurt my own personal development process

Before I defend my re-phrasing of Brian’s statement, let me say that he does have a couple of really good ideas in the post (specifically about the dilemma of databases), and I don’t intend to convince you that mocking and stubbing are inherently good ideas that will save the world, or that Brian’s process would be improved by adding mocks and stubs to it.

But Brian makes a broad generalization, attacking ideas that many view as inherently useful in the appropriate context, and I feel that the scope of his statement requires a bit of narrowing.

cucumbers creating cucumbers

I’m preparing to present a practical demonstration of rspec, cucumber and friends at Rails Summit Latin America this Thursday.

I was playing with the idea of using cucumber/rspec to drive the development of a cucumber browser/editor, but I’ve decided that it ends up being a bit too meta for a conference presentation.

Of course, nothing is too meta for a blog post, and clearly I’ve already procrastinated a great deal if it’s just a few days before and I’m still prototyping the app, so why not take some more time away from what I should be doing and post this sillyness?

And with that … enjoy!

Feature: create feature

So that I can easily create new feature
As a stakeholder
I want to create a feature in a browser

Scenario: create feature
  When I create a new feature named "Eat Cheese"
  And I give "Eat Cheese" the narrative:
    So that I can be happy
    As a cheese-loving person
    I want to eat cheese
  And I add a scenario to "Eat Cheese" named "roquefort"
  And I add a step to "roquefort" with "Given I am holding my nose"
  And I add a step to "roquefort" with "When I eat a hunk o' roquefort"
  And I add a step to "roquefort" with "Then I should smile at its deliciousness"

  And I save and run the "roquefort" scenario

  Then I should see "3 steps pending"
  And I should see:
    You can use these snippets to implement pending steps:

    Given /^I am holding my nose$/ do
    end

    When /^I eat a hunk o' roquefort$/ do
    end

    Then /^I should smile at its deliciousness$/ do
    end

DISCLAIMER: This also assumes some support for multi-line steps that has not yet been implemented and may not be supported as depicted here. So please don’t try this at home.

Cucumber

Aslak Hellesøy’s Cucumber library will be replacing RSpec’s Story Runner after the RSpec 1.1.5 release (coming soon).

Cucumber is a bottom up re-write of the Story Runner, and features a grammar parser using Treetop. When I first started working on support for plain text stories, I chose to roll my own parsing rather than writing a grammar for a number of reasons, but it turns out that we get some great benefits from it.

Cucumber supports multiple spoken languages:

So now you can say:

Funcionalidade: Adição
Para evitar erros bobos
Como um péssimo matemático
Eu quero saber como somar dois números

Cenário: Adicionar dois números
  Dado que eu digitei 50 na calculadora
  E que eu digitei 70 na calculadora
  Quando eu aperto o botão de soma
  Então o resultado na calculadora deve ser 120

Que legal! (How cool is that?)

There are already several languages supported, and adding new ones is fairly trivial, so we’ll likely support adding your own languages after some time.

Improved backtraces

Cucumber includes line numbers from the plain text Feature files, making it much, much easier to understand failures. (NOTE: we’re calling them Features now instead of Stories – look for another post on that subject soon)

Simpler configuration

Cucumber eliminates steps_for and using_steps_for. Simply define steps using the Given, When and Then methods:

#features/steps/accounts.rb
Given /I have \$(\d+) in my (.*) account/ do |dollars, account_type|
...
end

Now require the files with the step definitions you need:

cucumber -r features/steps/accounts.rb features/transfer_money

… and you’re off. For most cases you don’t even need that granularity, you can just say …

<code>cucumber features</code>

… and cucumber will require any .rb files it finds in the features directory before running the feature files.

Fewer surprises

When RSpec’s Story Runner finds more than one step definition that can handle a step, the first one it finds wins. This can lead to some painful debugging sessions.

When Cucumber finds more than one step definition that can handle a step, you get an error telling you which step definitions are competing, including their location (file and line number), so you can easily see and resolve the conflict.

What this means for you if you’re already using Story Runner

Cucumber is only a few months old and is nearly feature compatible with RSpec’s Story Runner and already adds a lot of powerful new features. Aslak has converted many, many stories to cucumber features, and is posting about his experiences and refining the process as he goes. By the time we release cucumber as the official scenario runner, the migration path will be well documented and inexpensive.

As for a time frame, that’s difficult to say. We’ve been promising the 1.1.5 release for some time and for one reason or another it keeps getting pushed back. We’ll likely wait for the rails 2.2 release and make sure that it is compatible. Rumor has it that is coming soon, but it was coming soon several weeks ago as well, so we’ll have to wait and see.

In the mean time, we are freezing development on Story Runner so that we can focus on Cucumber development. Before we officially release cucumber as part of rspec, we’ll create a separate project up on github for just the story runner (likely named rspec-stories) so the code will be available for teams that want to continue to use and/or maintain it.

I’ll follow up here and on the rspec-users mailing list (which is mirrored by the rspec google group) as things progress.

How I got started programming

Tagged by Bryan Helmkamp.

How old were you when you started programming?

Mid 20’s for a minute. Then mid 30’s for real.

How did you get started programming?

There were two starts. First, a tiny bit of background.

My father was a programmer for a minute (great stories about tripping on the way to deliver his final project in school, punch cards flying through the air in every-which direction), and my step father worked for Chemical Bank’s Data Processing department during my childhood. So I had some exposure to the results of programming as a kid, but had never really looked at any code. I was too busy shuffling cards and making coins disappear (my middle name, dare I admit it, is Arthur).

At 23, I was getting started as a musician (my 2nd career) and sought a non-music gig to get me through. A friend hooked me up as the copy-room attendent at an engineering firm. Apparently, this job had been occupied by people even less responsible that I was (a tough thing to be next to me at that age) because everyone was shocked when a couple of weeks in I was done each day by early afternoon and offering to help in other areas the rest of the day.

That’s when the guy who ran IT gave me the keys to the castle: a user account on their DIGITAL network and access to the giant BASIC manuals. I read through them in my spare time and wrote my first program: a musical scale generator. It went something like this:

$ What root?
D
$ What quality?
minor
$ D E F G A Bb C D
$ What root?

Dig the simple UI! I got it to handle major, natural, harmonic and melodic minor scales and all the modes of each. I was quite proud.

Eventually I made the ill-fated, romantic decision to earn all of my pennies from musical endeavors, so I quit at the engineering firm, turning in my keys to the castle, and started teaching guitar to fill that hole in my pocket.

Skip ahead 10 or 15 years. By then (1998) had a degree in music and was earning a living playing, teaching and arranging music. You’ve heard this story before. All the money came from gigs that involved tuxedos or commercials, and I got through those gigs to sponsor my habit of playing jazz and rock in clubs for beer money.

During this time, I had acquired an Apple Performa (remember?), had figured out how to make simple html websites and was helping some musician friends with theirs. I really, really enjoyed this, and my girlfriend noticed that I smiled when I made a website or played Windows (unfortunately titled, but beautiful tune by Chick Corea) but I frowned when I played Celebration. She suggested that I should consider taking a programming course and trying to get a part time job somewhere, thus sponsoring the smiley parts of my music habit with something that also made me smile.

This was the late 90’s, smack in the middle of the bubble, and it seemed like a perfectly reasonable idea. Little did I know that 10 years later I’d have hung my guitars in the closet to work on OSS. They’re still hanging there. This makes me sad. But I haven’t sold them yet, so there is still hope.

Anyhow, I took a couple of classes at a local community college, got a job working in the school’s IT department, and here we are 10 years later.

What was your first language?

BASIC

What was the first real program you wrote?

Depends on your definition of real. I wrote an applet when I was in school that let you try to solve a brain teaser. The first thing I got paid money to write that actually got used by people to make other money was an online training course for hospital administrators to learn how to fill out government forms properly.

What languages have you used since you started programming?

In no particular order: Ruby, Javascript, Python, Java, C#, PHP, Cold Fusion (OK, that is in a particular order).

What was your first professional programming gig?

The aforementioned hospital admin training course.

If there is one thing you learned along the way that you would tell new developers, what would it be?

Best practices are the serpent. They have the potential to be useful, but they are only useful in a very limited set of contexts, and they are applied just as often (if not more often) in the wrong ones as the right ones. Take the blinders off.

What’s the most fun you’ve ever had programming?

Working on an accounting-support system for a non-profit. This was about a year long project. We were doing (mostly) XP, pairing, and working closely with the people who actually used the software. The team included Micah Martin, Paul Pagel and Craig Demyanovich, all three of whom contributed to pairing sessions filled with a great balance of joy and head-butting. I learned a ton and had a great time doing it.

Aside from the fun we had programming, there were a lot of fun non-programming moments as well. I think my favorite on that gig was one morning when we were all arriving at the office. We had started greeting each other in different languages, Micah in French, me in Portuguese, and Christine (a woman who was not on the team, but worked close by) in Italian. Craig had joined us for the first time since we had been doing this, and he caught on rather quickly:

Me: bom dia, gente!

Micah: bon jour!

Christine: buongiorno!

Craig: puts “hello”

Another great moment on that project was sitting down with a user and watching her do her data entry job. She was really, really, really fast. But there was this one part in the process where she would pause for each entry. She wasn’t even aware of it, but recognized the problem when I brought it to her attention. So we made a small change to the UI and got rid of this small, but meaningful impediment and her life got just a little better. Very satisfying.

Up Next

Here are some folks who don’t seem to have been tagged yet. Let’s see if they’ll play …

new controller examples

There’s been a lot of discussion about clarity over DRY lately. This is something that I’ve been espousing for some time, but recent posts by Jay Fields, Mikel Lindsaar and Dan North have gotten me thinking about it again with more focus.

With this in mind, I’ve been refining the examples generated for restful controllers when you run script/generate rspec_scaffold with the rspec-rails plugin. I’ve got them now where I’m pretty happy with them, but I’m curious to hear what you think. I’m not going to tell you what I changed or what to look for, I’m just going to ask you to look it over and post your comments.

There are two listings: the generated code and the output you get from running the examples. Thanks in advance for any feedback.

script/generate rspec_scaffold account

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe AccountsController do

def mock_account(stubs={})
  stubs = {
    :save => true,
    :update_attributes => true,
    :destroy => true,
    :to_xml => ''
  }.merge(stubs)
  @mock_account ||= mock_model(Account, stubs)
end

describe "responding to GET /accounts" do

  it "should succeed" do
    Account.stub!(:find)
    get :index
    response.should be_success
  end

  it "should render the 'index' template" do
    Account.stub!(:find)
    get :index
    response.should render_template('index')
  end

  it "should find all accounts" do
    Account.should_receive(:find).with(:all)
    get :index
  end

  it "should assign the found accounts for the view" do
    Account.should_receive(:find).and_return([mock_account])
    get :index
    assigns[:accounts].should == [mock_account]
  end

end

describe "responding to GET /accounts.xml" do

  before(:each) do
    request.env["HTTP_ACCEPT"] = "application/xml"
  end

  it "should succeed" do
    Account.stub!(:find).and_return([])
    get :index
    response.should be_success
  end

  it "should find all accounts" do
    Account.should_receive(:find).with(:all).and_return([])
    get :index
  end

  it "should render the found accounts as xml" do
    Account.should_receive(:find).and_return(accounts = mock("Array of Accounts"))
    accounts.should_receive(:to_xml).and_return("generated XML")
    get :index
    response.body.should == "generated XML"
  end

end

describe "responding to GET /accounts/1" do

  it "should succeed" do
    Account.stub!(:find)
    get :show, :id => "1"
    response.should be_success
  end

  it "should render the 'show' template" do
    Account.stub!(:find)
    get :show, :id => "1"
    response.should render_template('show')
  end

  it "should find the requested account" do
    Account.should_receive(:find).with("37")
    get :show, :id => "37"
  end

  it "should assign the found account for the view" do
    Account.should_receive(:find).and_return(mock_account)
    get :show, :id => "1"
    assigns[:account].should equal(mock_account)
  end

end

describe "responding to GET /accounts/1.xml" do

  before(:each) do
    request.env["HTTP_ACCEPT"] = "application/xml"
  end

  it "should succeed" do
    Account.stub!(:find).and_return(mock_account)
    get :show, :id => "1"
    response.should be_success
  end

  it "should find the account requested" do
    Account.should_receive(:find).with("37").and_return(mock_account)
    get :show, :id => "37"
  end

  it "should render the found account as xml" do
    Account.should_receive(:find).and_return(mock_account)
    mock_account.should_receive(:to_xml).and_return("generated XML")
    get :show, :id => "1"
    response.body.should == "generated XML"
  end

end

describe "responding to GET /accounts/new" do

  it "should succeed" do
    get :new
    response.should be_success
  end

  it "should render the 'new' template" do
    get :new
    response.should render_template('new')
  end

  it "should create a new account" do
    Account.should_receive(:new)
    get :new
  end

  it "should assign the new account for the view" do
    Account.should_receive(:new).and_return(mock_account)
    get :new
    assigns[:account].should equal(mock_account)
  end

end

describe "responding to GET /accounts/1/edit" do

  it "should succeed" do
    Account.stub!(:find)
    get :edit, :id => "1"
    response.should be_success
  end

  it "should render the 'edit' template" do
    Account.stub!(:find)
    get :edit, :id => "1"
    response.should render_template('edit')
  end

  it "should find the requested account" do
    Account.should_receive(:find).with("37")
    get :edit, :id => "37"
  end

  it "should assign the found Account for the view" do
    Account.should_receive(:find).and_return(mock_account)
    get :edit, :id => "1"
    assigns[:account].should equal(mock_account)
  end

end

describe "responding to POST /accounts" do

  describe "with successful save" do

    it "should create a new account" do
      Account.should_receive(:new).with({'these' => 'params'}).and_return(mock_account)
      post :create, :account => {:these => 'params'}
    end

    it "should assign the created account for the view" do
      Account.stub!(:new).and_return(mock_account)
      post :create, :account => {}
      assigns(:account).should equal(mock_account)
    end

    it "should redirect to the created account" do
      Account.stub!(:new).and_return(mock_account)
      post :create, :account => {}
      response.should redirect_to(account_url(mock_account))
    end

  end

  describe "with failed save" do

    it "should create a new account" do
      Account.should_receive(:new).with({'these' => 'params'}).and_return(mock_account(:save => false))
      post :create, :account => {:these => 'params'}
    end

    it "should assign the invalid account for the view" do
      Account.stub!(:new).and_return(mock_account(:save => false))
      post :create, :account => {}
      assigns(:account).should equal(mock_account)
    end

    it "should re-render the 'new' template" do
      Account.stub!(:new).and_return(mock_account(:save => false))
      post :create, :account => {}
      response.should render_template('new')
    end

  end

end

describe "responding to PUT /accounts/1" do

  describe "with successful update" do

    it "should find the requested account" do
      Account.should_receive(:find).with("37").and_return(mock_account)
      put :update, :id => "37"
    end

    it "should update the found account" do
      Account.stub!(:find).and_return(mock_account)
      mock_account.should_receive(:update_attributes).with({'these' => 'params'})
      put :update, :id => "1", :account => {:these => 'params'}
    end

    it "should assign the found account for the view" do
      Account.stub!(:find).and_return(mock_account)
      put :update, :id => "1"
      assigns(:account).should equal(mock_account)
    end

    it "should redirect to the account" do
      Account.stub!(:find).and_return(mock_account)
      put :update, :id => "1"
      response.should redirect_to(account_url(mock_account))
    end

  end

  describe "with failed update" do

    it "should find the requested account" do
      Account.should_receive(:find).with("37").and_return(mock_account(:update_attributes => false))
      put :update, :id => "37"
    end

    it "should update the found account" do
      Account.stub!(:find).and_return(mock_account)
      mock_account.should_receive(:update_attributes).with({'these' => 'params'})
      put :update, :id => "1", :account => {:these => 'params'}
    end

    it "should assign the found account for the view" do
      Account.stub!(:find).and_return(mock_account(:update_attributes => false))
      put :update, :id => "1"
      assigns(:account).should equal(mock_account)
    end

    it "should re-render the 'edit' template" do
      Account.stub!(:find).and_return(mock_account(:update_attributes => false))
      put :update, :id => "1"
      response.should render_template('edit')
    end

  end

end

describe "responding to DELETE /accounts/1" do

  it "should find the account requested" do
    Account.should_receive(:find).with("37").and_return(mock_account)
    delete :destroy, :id => "37"
  end

  it "should call destroy on the found account" do
    Account.stub!(:find).and_return(mock_account)
    mock_account.should_receive(:destroy)
    delete :destroy, :id => "1"
  end

  it "should redirect to the accounts list" do
    Account.stub!(:find).and_return(mock_account)
    delete :destroy, :id => "1"
    response.should redirect_to(accounts_url)
  end

end

end

script/spec spec/controllers/accounts_controller_spec.rb -fn

AccountsController
responding to GET /accounts
  should succeed
  should render the 'index' template
  should find all accounts
  should assign the found accounts for the view
responding to GET /accounts.xml
  should succeed
  should find all accounts
  should render the found accounts as xml
responding to GET /accounts/1
  should succeed
  should render the 'show' template
  should find the requested account
  should assign the found account for the view
responding to GET /accounts/1.xml
  should succeed
  should find the account requested
  should render the found account as xml
responding to GET /accounts/new
  should succeed
  should render the 'new' template
  should create a new account
  should assign the new account for the view
responding to GET /accounts/1/edit
  should succeed
  should render the 'edit' template
  should find the requested account
  should assign the found Account for the view
responding to POST /accounts
  with successful save
    should create a new account
    should assign the created account for the view
    should redirect to the created account
  with failed save
    should create a new account
    should assign the invalid account for the view
    should re-render the 'new' template
responding to PUT /accounts/1
  with successful update
    should find the requested account
    should update the found account
    should assign the found account for the view
    should redirect to the account
  with failed update
    should find the requested account
    should update the found account
    should assign the found account for the view
    should re-render the 'edit' template
responding to DELETE /accounts/1
  should find the account requested
  should call destroy on the found account
  should redirect to the accounts list

Thoughts on the Dance-Off

In his Great Test Framework Dance-Off at RailsConf 2008, Josh Susser compared rspec with test/spec and shoulda. All in all I’d say he was very fair in his comparisons and I’d recommend checking out his slides.

There were a couple of dings Josh handed rspec and I’d like to respond to them. This is not intended to sell you on using rspec if you’re not already using it. In fact, you’ll see that I agree with a some of Josh’s criticisms.

I’d much rather see developers using frameworks like test/spec and shoulda than not using anything more expressive than test/unit out of the box, but I also cringe when I hear that someone chose a different framework for reasons that are based on inaccurate or incomplete information. I take full responsibility for that. If you’re basing decisions on inaccurate or incomplete information it’s because I haven’t made the accurate and complete information available to you. This post is one step towards addressing that problem.

RSpec waving ‘bye bye’ to implicit module inclusion

Until sometime very soon, when you describe a module in RSpec using this syntax:

describe SomeModule do
...
end

RSpec implicitly includes that module in the example group. This allows you to do this:

describe CatLikeBehaviour do
it "should say 'meow' when it greets you" do
  say_hello.should == 'meow'
end
end

module CatLikeBehaviour do
def say_hello
  'meow'
end
end

As is often the case with things implicit, this actually turns out to be a problem. The problem revealed itself most notably when an RSpec user reported that a describe() method in a module he was using was conflicting with RSpec’s describe() method.

One response to that thread suggested that using describe() in a module might be too generic, but I think that really hides the point. Imagine how frustrated you would get if you had examples of a module with a current? method and we decided to add a current? method to Spec::Example::ExampleGroupMethods. You’d suddenly start seeing those examples fail with stack traces eminating from RSpec instead of your code. Not good.

And so, we are going to be removing this feature.

With the 1.1.4 release, you get a warning any time that the example calls a method on self that is part of the included module. Soon it will be removed entirely.

rails helper examples

The biggest impact of this is going to be felt in rspec-rails helper examples. There are two remedies that you have if you’ve got examples that send messages to self that should be going to another object:

  1. use the new helper object provided by HelperExampleGroup

    describe DateHelper do it “should format the date as mm/dd/yyyy” do helper.format_date(Date.new(2008, 5, 31)).should == ‘05/31/2008’ end end

The helper object is an instance of ActionView::Base with the named module included in it, so it has access to everything else that it should have.

  1. include the module explicitly

    describe DateHelper do include DateHelper it “should format the date as mm/dd/yyyy” do format_date(Date.new(2008, 5, 31)).should == ‘05/31/2008’ end end

My recommendation is definitely the first option as I find it more expressive.

I realize this is API changing and backward-compatibility breaking, but this is one of those cases where, at least in my view, the pain is justified by the result.

RSpec-1.1.4

We released RSpec-1.1.4 today. It’s mostly a maintenance release but there are a few of cool new features that you may want to know about and take advantage of.

hash_including

One thing that has always been a drag is having to specify every key/value pair in a hash that is received as an argument. This is especially painful in Rails controller examples because Rails adds some data to the hash and the examples really don’t care about that extra data.

Enter hash_including().

This is a mock argument matcher that let’s you expect a hash including certain key/value pairs regardless of anything else that shows up in the hash. So instead of:

account.should_receive(:deposit).with({:amount => 37.42, :date => anything()})

you can just say:

account.should_receive(:deposit).with(hash_including(:amount => 37.42))

and keep the example focused on what you’re really interested in

Thanks to Rick DeNatale who submitted this feature request and the patch to implement it.

The heckler returns

RSpec wasn’t correctly supporting heckle for a while but the spec-heckler is back in action. For those unfamiliar, you can read about heckle at zenspider’s blog.

Here’s how you heckle your Animal model in your PetStore app:

spec spec/models/animal_spec.rb --heckle Animal

Thanks to Antti Tarvainen for resurrecting this one.

stub_model

This is for rails developers who like writing view examples with mock_model() but are sick and tired of having to stub every single attribute that gets referenced in a view.

Instead of creating a mock object like mock_model() does, stub_model() creates an instance of a real model class, but cuts off it’s connection to the database, raising an error any time it tries to connect to the database.

This is inspired by projects like unit_record and NullDB, but let’s you do things at a more granular level – allowing you to hit the db in some cases (where you think you really need it) and not in others.

Of course, you may prefer to the sort of “protection” you get from those projects, which ensure that no code touches the DB at all. If you do, have at it. This is just another option for you.

All this and more

These are just a few of the issues addressed in 1.1.4. For more information, check out the changelog and lighthouse.