From Fixtures to Factories
Automated tests need example data, and it’s a pain to have to construct a complete object in every test, especially when there are a lot of non-optional fields.
The standard improvement is to use fixtures, a file with some example data, that your tests can load by giving the name of a fixture. Here’s one of the fixtures for a gamer:
alice: # alice is heavily involved id: 1 mail: alice@example.com handle: alice password: red queen teaser: Follow the white rabbit. profile: Not so plain, after all. homepage: http://www.example.com jabber: alice@example.org location_id: 3 last_login: 2006-10-30 11:56:08 created_at: 2006-10-30 11:56:08 location_at: 2006-10-30 11:56:08 tag_text: dnd, wod mail_on_message: true mail_on_nearby_discussion: true
This is straightforward, but location_id is the id of another fixture in another file. As you can imagine it’s pretty easy to get them out-of-sync and break some tests. I end up leaving little comments in the YAML to remind myself of the cross-reference.
It’s awfully tempting to reuse fixtures from test to test. Maybe Alice was written to test logging in with a username and password and next I’m writing the test of logging in with an email address and a password. I could copy and paste Alice to another fixture to reuse in that test, but as a programmer I have a pathological aversion to copy and paste. Alice is going to get reused (most of those fields are optional and were used by very different tests), and after this happens a few times it’s hard to change a fixture without breaking an unrelated test.
I’ve gotten a lot of use out of fixtures as I’ve developed the habit of testing all my code, but reuse has made my tests somewhat brittle. After a bit of research, I’ve moved over to using factories.
Here’s the factory for a gamer:
Factory.sequence :handle { |n| "Gamer_#{n}" } Factory.define :gamer do |g| g.handle { Factory.next :handle } g.mail { |m| "#{m.handle}@example.com" } g.password 'secret' end
Only the essential, required fields are listed. Using this simple template, the tests themselves specify the values of the fields they’ll be testing.
def test_login_with_valid_username_and_password gamer = Gamer.create :handle => 'Alice', :password => 'red queen' ... end def test_login_with_valid_email_and_password gamer = Gamer.create :mail => 'alice@example.com', :password => 'red queen' ... end
Ideally, changing a fixture wouldn’t break any tests at all. And factory_girl (what I’m using for factories in Rails) makes associations easy:
Factory.define :post do |p| p.association :discussion, :factory => :discussion p.association :poster, :factory => :gamer p.created_at Time.now.utc p.body "Post Body" end
Now my tests include the specific data they care about, making them easier to understand and improve. And they don’t interrelate, so they’re much more reliable. I do still use fixtures, but now they’re exclusively for tests that need to deal with real-world examples.
Want more? I'm not as good at forgetting to update @pushcx on Twitter.
Conversation (3)
Jump to comment form | comments rss [?]Add this link to your feed reader to watch for new replies to this post. | trackback uri [?]Trackback this link in a blog post to create a comment here linking to your post.