11 April 2008

Shoulda and object_daddy Sitting in a Tree, t-e-s-t-i-n-g

Like some other folks working with Rails, I've been a bit frustrated with Fixtures. Foxy Fixtures, Rathole, and such things, including what is in Rails 2, have helped a lot. However, the two biggest frustrations for me come down to the fragility of fixtures, and knowing what fixtures you have and how they relate to their associated fixtures. I would find myself thinking, "hmm, which user do I use when I want the one with X associations" or what not. Naming your fixtures well helps, but only so much.

Lately, I've come across two plugins that I am really loving. These are Shoulda and object_daddy. Shoulda seems to be gaining in popularity in the Rails community, which doesn't surprise me. It gives you some of the best syntax of RSpec, without having to use RSpec (which I am not all in love with, unlike various others), as well as it gives you some nice "should" methods, and other features I'll get into in a minute.

object_daddy I think is rather obscure. I only found out about it due to Tammer Saleh's presentation (video link) on Shoulda from the 2008 MountainWest RubyConf. object_daddy has improved in its short life, and is quite useful today. What it does, is provide a factory/generator mechanism for creating model objects. I've done this in the past with object constructors or factories, etc., but object_daddy organizes all this, and provides a slick mechanism, called "exemplars", that specify how model attributes are defined when generating objects, and more importantly, when generating multiple objects. Tammer covers this issue in his presentation, which I highly recommend watching.

It's taken me a bit of time/use of Shoulda to get really into it, but like so many things, use it a bit and then the light bulb not only goes off, but seems to erupt with light. The big one here for me was how to leverage contexts, and by that I really mean nested contexts. My tests now not only read better, but can be written in a much nicer fashion, as well as organized in a great way. On the organization front for example, I now often have two top level contexts in a functional test: one for cases where I'm testing actions without a user being logged in, and the other for when a user is logged in. Great way to group them.

But, what's got me most excited lately is the combination of these two testing tools, and what it's done to my tests. First, I've darn near eliminated fixtures on the project I'm using this most with so far. This has removed the fragility, as well as it's just FAR easier to understand a test case's setup/scenario. I use Shoulda's contexts and setup, combined with generated objects from object_daddy to create "scenarios" (to steal the term from the plugin that provides this kind of thing for fixtures). The benefit is that you have all of the info about your test right there in one place in front of you. You don't need to bounce between likely multiple fixture files and your test code file to ascertain what's being used in your test. Plus, you can be very specific about the data being used in that particular test (or tests).

I heartily recommend you try this out. Two other things of note:


  • Shoulda can be mixed in with existing TestUnit. So, you can slowly convert to it, or just build new tests with it, etc. Very nice. And, it doesn't require anything special to run the tests (it really is just a method generator for building TestUnit tests).

  • Check out Shoulda's "should_eventually" method. I'm making more and more use of this, as I use a Test First approach. So, I go in, and build lots of tests, and do a lot of "should_eventually" as I think of things to test and functionality I need, etc. Then as I determine how to write those tests, and following on from that, write the implementation, you simply remove the "_eventually" and let it rip.



Note, if you are an RSpec fan, you can of course achieve the same as Shoulda for contexts and such (I think anyway), so just pull in object_daddy and leverage that aspect.

And last, but not least, I've forked object_daddy to make one tiny change (a single line, actually, a single method call name change!) that's made a big difference for me (comments very welcome). This change is to, by default, call create in the generate method that object_daddy adds to your ActiveRecord objects, instead of calling new. This avoids what I found to be the common case of doing:

my_new_object = SomeModel.generate
my_new_object.save
my_new_object # or some use of it

Now, you can simply call SomeModel.generate, and use that inline, knowing it's saved in the DB, etc. I want to take a look at adding options to generate, or additional generate methods that provide the flexibility to use new, or create! or such things, for the cases where those are needed. My fork is hosted on GitHub, and is public, so feel free to check it out: http://github.com/chris/object_daddy.

p.s. For those that wonder why I've "all but eliminated fixtures", as in, what do I still have in fixtures? The only things are standard data, which in my case amounts to a couple specific users, and a couple of Roles and Permissions. These normally get setup in the DB migrations, but I'm working through what Rails does when you run tests and it wipes the DB clean (and thus doesn't pick up seed data from migrations), and other such issues.

32 comments:

Trent Brown said...

Thanks much for the info on this subject, Chris. It's very timely for me: I was just about to jump on the RSpec bandwagon, in spite of liking the look of Shoulda better. I think I'll take Shoulda for a ride, now. I'd not heard of Object Daddy before. Looks real promising. I'd be interested to hear more about your experiences using it.

Chris said...

Hey Trent, hope all is well (you're still at Adobe right?). Anyway, ya, Shoulda is great, definitely worth looking into.

object_daddy is excellent. There are a few surprises (well, I think) here and there when writing generators, and their docs don't match up to how it works quite (the main reason I made my fork and fixed/changed the generate method to call create instead of new; so you can actually write code like they show in their docs :)

One caveat I just ran into, which makes sense in hindsight, is, don't use a method call as a value for a generator. For example, if you had:

generator_for :user => User.generate

That will not work properly. Instead, you want to do:

generator_for :user do
User.generate
end

Feel free to ping me more on it if you like. Will be interested to see how you like it, and how others are using it, etc.

Anonymous said...

this site is just what i need some good info on the code

Gasent said...

Object_daddy looks real complicated. I think it should be simplified, it has a HUGE learning Curve.
My Site

Anonymous said...

This would help my friends a lot..=)

ladyinpurple said...

Very helpful posts...good choice of your blog title :)

Chris said...

@mental it's actually not complicated. Take a look at the docs and such. Basically, all you have to do to use it at the most basic is to install the plugin, and then call YourModelObject.generate whenever you need to make an object of that type. Usually you're going to want to customize how the generate works though, and I personally prefer to use "exemplars" for that, so as to keep that code separate from my main model definitions.

To use exemplars, it amounts to creating a new file in the test/exemplars directory, and then just adding lines like:

YourModelObject.generator_for :user do
User.generate
end

Or something like:

YourModelObject.generator_for :weight do |prev|
prev.succ
end

Usually your generators can be that simple, sometimes even simpler if you just want the same value all the time, you can do:

YourModelObject.generator_for :weight => 100

Try it out, it's actually quite easy. Even the object_daddy code itself is contained in a single file that is not long.

Melodieann Whiteley said...

Thanks for this info. My daughter will be thrilled when I tell her about your blog. It is just what she needs!

Nicey said...

Nice one - I ve learnt something today !!

Anonymous1 said...

I saw some stuff I understood but for the most part, I didn't understand anything. Well, ok.

Archit said...

i have learnt something new

The Sparkling Thought.... said...

Nice post>

DL said...

I would like to learn more about it. can you provide me some guidance. Please....?

rbradshaw said...

Great post. Looking forward to learning more on the subject.

Chris said...

@Deepak The best way to learn more about each of these is to visit the links I provided for them. You really don't even have to read the full object_daddy page, just scroll down to the very end where he talks about object_daddy, this part is rather short. Or, download/install the plugin and read the README file, that will get you going. As for Shoulda, their page is a great simple tutorial, so surf over to that link and check it out. You'll be up and running in no time.

Justin J said...

I was understanding only about 50% of all that. I wonder if people just absorb all that info by immersing themselves in it, or if it come only through study...

Rick Bradley said...

Chris,
Thanks for the great writeup and the feedback on object_daddy. We're moving our projects to github shortly so maybe we can more readily exchange patches.

It's good to see someone using the plugin and really kicking the tires. We use it all the time in our work but we don't stress it as much as when other people use it.

I'll ping you back when we get set up on github.

Thanks!
Rick

Chris said...

Rick, definitely let me know when you go to GitHub. I will then do a fork from your project there so they can be connected, etc. Did you look at the tiny (yet significant) change I made? I wonder if either a) you guys had it this way originally and then needed to change it for some reason, due to the way the docs are written (because as written the docs don't work/aren't right), or b) if it is just an oversight in the docs, or??? Anyway, it'll be great to have it all on GitHub so we can push/pull as needed. Thanks!

Yossef Mendelssohn said...

Chris,

Yossef here (another OGC/flogic guy, the 'ymendel' you see on b.logi.cx).

After some futzing with SSH keys, the github move has been effected. I'm going to look into your comments (here and b.logi.cx) and see what changes need to be made.

http://b.logi.cx/2008/4/15/gitting-on-the-bandwagon
http://github.com/flogic

Chris said...

@Yossef, thanks! As you may have seen, I killed my git version, then forked yours, made my change, then sent you guys a pull request. I understand if the change is not what you may want, but figured I'd send this to see how it all works, and since you mentioned you wanted to look at it.

twinkle teaches said...

Congrats on being a blog of note! :) tina

Izabela said...

Nice... Keep on doing like hat. Check my place... Kiss to you all...

Anonymous said...

Thank you very much Chris.
Very helpful posts.
:)

Administrator said...

Good site...like the contents...and the presentation.

http://nullstress.blogspot.com

Author of How I said...

As I primary do development in .NET, this was a very interesting read. Opuno

Anonymous said...

hey nice post,it is interested again,and helpful,share this information to this site http://filestance.com/

Unknown said...

Chris,

I thought you might be interested in new developments and a new blog post about Object Daddy: http://b.logi.cx/2008/5/13/daddy-hasn-t-got-a-brand-new-bag

Thanks for the OD love, man (or at least the use).

Chris said...

ymendel thanks for the tip. I'm still making use of it, and with the enhancements/changes you guys made, I'm now simply using your GitHub version, and have killed my fork of it. Thanks!

otterpupp said...
This comment has been removed by the author.
Anonymous said...

Shoulda rocks! +1

I'll take Object Daddy for a test drive some day... seems a bit complex. I really like the factories-and-workers plugin:
http://code.google.com/p/factories-and-workers/

it uses slick metaprogramming to make create_{model} and valid_{model}_attributes methods

you can also just build, if you want to make an object without saving it. Seems to be more semantically rail-ish than "generate"

Chris said...

Hey David, do try object_daddy, it's really not complex, in fact, I think you'll find it makes things a bit more clear.

That said, it's funny, but since writing this post, I have actually been doing all my new development with RSpec. I will have to write another post on what convinced me to switch over. But, regardless, to me, there are actually only two testing framework choices: Should (which implies Test::Unit of course) and RSpec. Or, in other words, Shoulda should simply be a standard thing for Test::Unit based testing.

Anonymous said...

PS I forked factories-and-workers to github:

http://github.com/dfl/factories-and-workers/tree/master

Seems like the functionality is very similar to Object Daddy, but I prefer the syntax of Factories.