Wednesday, April 4, 2007

Why I Hate Test Fixtures (And What I Am Prepared To Do About It)

I have a few complaints against YAML test fixtures:
  • They break. Changing your database schema will often leave existing test fixtures invalid. I am extremely lazy, and it becomes a maintenance burden.
  • I hate switching between files in my editor. Already, I have to switch between my application code and my test code. Switching to a test fixture for something as small as a sample object gives me three files I have to switch between.
  • The test fixtures centralize concerns from all different test suites. For example, I have several users in my test fixture who are used I-don't-even-know-where among my test classes. It seems bizarre to think that while my tests should remain independent and modular, my test data should be entirely jumbled together.
  • I can't remember what is what within my test fixtures. I had users named to Bill, Peggy, Joe, Quentin. Who are these people? I switched to the slightly more manageable valid_user, invalid_user, valid_user_sending_message_to_invalid_user, invalid_user_receiving_message_to_valid_user. Even if I try to reuse the same fixture objects as often as possible, bloat eventually happens.
  • If objects have dependencies, they get tedious. Let us say your objects have dependencies on other objects. Your order requires a user to be valid. Your user requires an account. If I now have to make dummy fixtures for all these, I've got to switch between five or six files now.
In fairness, I have these positive things to say about externalized test fixtures:
  • Sometimes they really need to be externalized, as in when your test fixtures are entire documents, like PDFs or spreadsheets.
  • The YAML fixtures are a vast, vast improvement over using property files, XML, or writing a lot of object instantiation code within your test itself (all of which I used to do back in Java).
So what do I want instead? I want to create my fixtures within my tests in no more than a line or two with valid defaults:
def test_signup
user = User.sample
post :signup, { :user => user.attributes}
assert_equal 1, User.count
end
I want to create required dependencies automatically:
def test_signup_creates_account   
user = User.sample
post :signup, { :user => user.attributes}
assert_equal 1, Account.count
end
I want attributes to be overridable:
def test_signup_requires_email
user = User.sample(:email => nil)
post :signup, { :user => user.attributes}
assert_equal 0, User.count
end
I want even nested attributes to be overridable:
def test_signup_creates_account   
user = User.sample(:account => { :balance => 5.0 })
post :signup, { :user => user.attributes}
assert_equal 5.0, Account.find(1).balance
end
And I want to make minimal changes (none if possible) to my model classes to support this stuff.

Please take note that this kind a testing design pattern has cropped up both in integration testing and in RSpec.

ActiveRecord classes can introspect their associations and, with a plug-in, their validation. For 90% of cases, this should be all we need to create the object graph.

No comments: