[rspec-users] do I use mocking (vs fixtures) for methods that produce results based on non-trivial SQL queries

Ben Mabey ben at benmabey.com
Fri Sep 5 13:01:02 EDT 2008


Scott Taylor wrote:
>
> On Sep 5, 2008, at 11:40 AM, Ben Mabey wrote:
>
>> Scott Taylor wrote:
>>>
>>> On Sep 5, 2008, at 10:55 AM, Pat Maddox wrote:
>>>
>>>>> time.  Just do the math: If one database test takes 0.2 sec (a
>>>>> realistic
>>>>> figure), then 5 will take 1 sec, at 1000 over 3 minutes, and at 2000
>>>>> over 6
>>>>> minutes.  This is starting to get into "time for a
>>>>> coffee/cigarette/juggling
>>>>> what have you" breaks.  So - either you end up very hyped up on
>>>>> coffee not
>>>>> doing much work, or you don't run your test suite very often, which
>>>>> leads to
>>>>> legitimate bugs which can be solved. (At one point I had actually
>>>>> deployed
>>>>> code into production which had a serious bug.  This all could have
>>>>> been
>>>>> avoided because the test suite *was failing*, but I had gotten
>>>>> into the
>>>>> habit of not running it because it was too expensive).
>>>>
>>>> There's a couple things you can do to help with this:
>>>>
>>>> * Split slow tests into another dir and run them separately from your
>>>> fast suite.  Keep rspactor on your fast suite and run the slow tests
>>>> before checkin
>>>>
>>>> * Use a CI server
>>>>
>>>
>>> Yes, most certainly.  But the db tests •still* aren't cheap, meaning
>>> you'll need to wait for the CI server to run them there is a failure,
>>> or wait before checking in.
>>>
>>> Also - have you found a CI server that is working with git?
>>
>> On github you can find forks of cc.rb with added git support.  I have
>> been using it like a charm for months.
>>>
>>>>
>>>>> I've been working hard on solving this issue by building a SQL
>>>>> parser in
>>>>> treetop, and hope anyone who is interested will join me (just let me
>>>>> know if
>>>>> you are interested and I'll make the project publicly viewable on
>>>>> github).
>>>>
>> Thats cool.  I was actually talking to our DBA about the possibility of
>> this the other day.  I think for the simple cases something like this
>> would be great.  I have been using the nulldb adapter will a lot of
>> success recently but there are a few spots where just simple insert and
>> select statements would go a long ways.
>>
>>>> http://github.com/nkallen/arel/tree/master might also be interesting
>>>> to you.
>>>
>>> Hmm..Well, that's interesting, although I think that project, like so
>>> many others, is *generating* SQL, not parsing it, correct?
>>>
>>> I've made my sql parser public, and here is is if anyone wants to take
>>> a gander (although it most certainly is still a work in progress):
>>>
>>> http://github.com/smtlaissezfaire/guillotine/tree/master
>>
>> Awesome.  I'll for sure take a look at this.
>>>
>>> Scott
>>>
>>> _______________________________________________
>>> rspec-users mailing list
>>> rspec-users at rubyforge.org
>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>
>>
>> To answer the original poster's question.. I tend to isolate my SQL
>> calls to single methods and don't allow those methods to operate on the
>> results.  That way I can have a faster functional spec test that method
>> individually with a smaller data set in the DB.  I will then stub out
>> the calls to that object#method in my specs where the interesting
>> business logic uses that data to produce results.  I then have an
>> interaction test in form of a story that does more complex setup and
>> testing of both methods and the DB in unison.  My main motivation for
>> this separation is for speed of my specs that are for my business
>> logic.  If what I have said doesn't make sense I can given an example of
>> what I mean.
>
>
> Doesn't this violate the rule of "never stub the method under test?"
>
> Scott
>

I probably didn't explain that very well.  Simple example:

class WidgetRepository
  def self.some_method_that_isssues_complex_sql_to_find_the_data(*args)
    ....
  end
end

describe WidgetRepository,
".some_method_that_isssues_complex_sql_to_find_the_data" do
#allow this one to hit the db

  it "should blah, blah..." do
    widget = create_widget
     ...(more setup)...
   
WidgetRepository.some_method_that_isssues_complex_sql_to_find_the_data(*args).should
== expected_results
  end

  ...

end


Then we have an object that uses the data:

class WidgetYieldCalculator

  def calculate(widget, other_stuff)
    data  =
WidgetRepository.some_method_that_isssues_complex_sql_to_find_the_data(some_args)
    ..operate on data..
    return result
  end
end

Now to spec it, since the repository SQL call is all speced out, we can
simply stub the call to WidgetRepository.some_method... and rely on the
fact that the Respository will do it's job:

describe WidgetYieldCalculator, "#calculate" do
  it "should calculate the correct yield" do
    # given   
    data = [...]
    widget = ...
   
WidgetRepository.stub!(:some_method_that_isssues_complex_sql_to_find_the_data).and_return(data) 
             
               #depending on the situation and spec an expectation is
better than a stub
    # when
    result = WidgetYieldCalculator.new.calculate(widget, options)
    # then
    result.should == expected_results      
  end


end



Sorry I don't have more time to do a better example or explain it
better.  Basically, I was just advocating a separation of
responsibilities:  the data retrieval and operation on the data.  This
then allows you to mock your interface of the repository at a higher
level.  As I said, with the ActiveRecord pattern this kind of thinking
seems a bit odd.  In my rails projects the "repository" calls are
usually class methods on an AR class.  The big gain for this is that you
can then test all your business logic in the WidgetYieldCalculator by
varying the data which is stubbed.  This reduces the number of DB calls
considerably.  You will need an integration spec for both of these parts
to work together, and lately I have been starting with a story to drive
the entire process.  I wouldn't got to such lengths on simple cases, but
the current project I'm working on has driven me to do this in a number
of places where lots of data and objects are involved.

To answer your question more directly, I never stub a method of an
object that I am testing.. I just stub the collaborator's.

Hope that clears it up a little,
Ben


More information about the rspec-users mailing list