Model Generation – Coding for Simplicity

Rails convention calls for test data to be stored in fixtures. This works very well for the first few iterations, but this approach quickly becomes unwieldy. Often, many records are required, which can lead to poor record names like event_46. Even when naming conscientiously, it’s difficult to give each record a meaningful name in a global scope. When adding required fields to models, every record needs to be updated, which can be tedious for large test data sets, and very often the updates have an undesired impact on a broad set of tests.

We’d prefer to build up test data closer to home, in the test itself, so we can see the connection between the data and the expectations. Furthermore, if we structure our tests this way the data is isolated from other tests so we’re free to tinker with it without breaking those other tests. The drawback is that manual creation of ActiveRecord objects in your test code can become as tedious to write and maintain as fixtures.

Enter Generate – a technique that centralizes ActiveRecord model generation for test data, removes the need for fixtures, and avoids the tedium and verbosity of in-test model creation.

Example RSpec usage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
describe Event, '#find_all_by_name' do
  it 'finds events with matching names' do
    event = Generate.event
    Event.find_all_by_name(event.name).should include(event)
  end

  it 'does not find events with different names' do
    event = Generate.event(:name => "some name")
    Event.find_all_by_name("a different name").should include(event)
  end

  it 'does not find private events' do
    event = Generate.event(:private => true)
    Event.find_all_by_name(event.name).should_not include(event)
  end
end

class Generate
  def self.event(options={})
    Event.create!(options.reverse_merge(:name => "sample event name", :private => false))
  end
end

The Generate class makes building an Event very convenient. The test only needs to specify attributes it cares about (if any) and Generate will worry about making the Event valid. If at some point the event requires a starts_at field, the Generate class can simply add that to the default attributes, and all tests will continue to pass without alteration. This becomes especially useful when building webs of objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
describe Event, '#attendee_count' do
  it 'returns the number of yes rsvps' do
    event = Generate.event(:attendees => [Generate.user, Generate.user])
    event.attendee_count.should == 2
  end
end

class Generate
  def self.event(options={})
    attendees = options.delete(:attendees) || []
    event = Event.create!(options.reverse_merge(:name => "sample event name", :private => false))
    attendees.each do |attendee|
      Generate.rsvp(:event => event, :user => attendee)
    end
    event
  end

  def self.user(options={})
    @user_count ||= 0
    @user_count += 1
    User.create!(options.reverse_merge(:email => "user#{user_count}@example.com"))
  end

  def self.rsvp(options={})
    options[:user] ||= Generate.user
    options[:event] ||= Generate.event
    Rsvp.create!(options.reverse_merge(:response => "yes"))
  end
end

Once again, the test is very clear. A reader can tell exactly what’s going on without hunting through fixtures. Furthermore, adding attendees to the Event in this test will not affect any other tests.

We’re using this approach on a large project currently under development. As our project has grown, the complexity of our data model has increased, making our ActiveRecord models more and more interrelated. But rather than feeling the pain of fixture maintenance and test adjustment, we’re able to move freely and quickly by updating one or two Generate functions at a time without impacting any other tests, while maintaining the flexibility to test anything new that comes our way.

The nice thing about Generate is that it doesn’t require a supporting library or up-front investment in tool building, short of knowing how to use ActiveRecord to instantiate your models. The trick is in the technique itself – leaving you the option to apply it in your projects in whatever way best meets your needs.

References:

The Generate technique is very similar to the ObjectMother testing pattern. Please see “ObjectMother: Easing Test Object Creation in XP” by Peter Schuh and Stephanie Punke.

This entry was posted in Testing and tagged . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

5 Comments

  1. Posted April 30, 2008 at 9:30 pm

    If you haven’t already, take a look at Fixture Replacement. It’s pretty snazzy and tries to accomplish the same goal.

  2. Posted April 30, 2008 at 9:30 pm

    I hadn’t seen FixtureReplacement before, but it does look nice and snazzy. It has a few things we don’t. In particular it can do new_event and get an unsaved event with valid data.

    The only weakness I see in FixtureReplacement is that it doesn’t appear to allow creation of dependent objects in the same call. If I’m creating an event with several RSVPs, I need to build them separately.

    That said, FixtureReplacement looks like a very slick tool, and could probably be extended to support dependent object creation. It may already, but my cursory review didn’t come across any such support.

  3. Posted April 30, 2008 at 9:30 pm

    Here is another example of the same model. http://www.dcmanges.com/blog/38

  4. Chris Rittersdorf
    Posted April 30, 2008 at 9:30 pm

    Dave, the comment notifications are set up and working.

  5. Posted April 30, 2008 at 9:30 pm

    I read your blog for a long time and should tell that your posts are always valuable to readers.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">