My test-driven Ruby setup

May 12, 2009 - 8 Comments - ruby tdd tools

I am a relatively recent convert to the mantra of TDD:

  1. Write a failing test
  2. Make the test pass
  3. Refactor

It took my involvement in writing an analytics API for the iPhone to help me see the light. This API was receiving millions of requests per day from hundreds of thousands of unique users, so every deployment of new code had to be rock-solid.

Of course, back then I was just using vanilla Test::Unit; we didn’t know any better. Since then, as I urged my colleagues to give TDD a shot, I have researched a number of libraries which take a lot of the pain out of writing tests. Here follows a description of my testing stack, along with brief explanations of why I went with them over the alternatives.

Shoulda

I love Shoulda. Stringified test names + nestable contexts + easy macros makes for well-organized, concise tests. The readable output it generates is also a great feature; consider the test below:

When this is run and it invariably fails, we get the following output:

Failure has never before read so well.

So Shoulda provides some obvious advantages over vanilla Test::Unit, but why not go with RSpec like so many cool kids are doing?

For me, it came down to a question of Occam’s Razor. It seems from my research that RSpec’s goal is to help people understand the value of testing by making test code read more like English. I can see how that might be effective in producing the “Ah ha!” that so many developers still need before they see the value in writing tests, but I personally don’t need any further convincing, so RSpec’s fancy syntax seemed extraneous and just another thing I would have to learn when the typical assert methods already work just fine. A report of strange issues with RSpec which drove a switch to Shoulda probably also influenced my choice.

Shoulda makes me happy; I shoulda been using it since a long time ago.

Machinist

Have you ever actually written test data using Rails’ built-in fixtures feature? It’s a nightmare. Even after the introduction of named fixtures, it was still a disorienting mental dance trying to remember all of them when editing them or using them in your tests. Fortunately, alternatives came along which take a more generative approach, allowing you to create valid objects as your tests need for them, specifying only the attributes which need to differ from the default blueprint.

This is definitely the right way to go for test data. Two solutions available at this time are Factory Girl and Machinist. Lots of people seem to like Factory Girl, and it’s a great solution and if I recall correctly it came around first, but given the choice I went with Machinist for its more concise blueprint DSL.

Compare the following example from Factory Girl:

with the equivalent blueprint for Machinist:

Same number of lines, but Machinist’s lines are consistently shorter, which means fewer keystrokes to define a blueprint. It’s also more readable in my opinion.

As far as I can tell, there is just one thing that Factory Girl has which Machinist lacks, and that is the ability to define a new factory which inherits from another factory. This would be useful if you have a basic User factory followed by more specific types of Users. So far, I have found that I can get by just fine without this feature, but it would still be a fine addition to Machinist.

Autotest

If you do TDD with Ruby, you should absolutely be using autotest. I like to just keep autotest running in a second monitor while I edit code in the other. It’s very intelligent about keeping things speedy by following you around your code, running only the tests you need to be focusing on, the tests which last failed, until you get them passing again, at which point it runs the whole suite again to ensure that no other regressions occurred as a result of your fixes.

What I’m trying to convey is that autotest does more than merely save you the effort of running your tests manually. It makes testing utterly integral to your coding experience while leading you in the right direction within the context of TDD. It helps you focus into a Zen-like state of flow. It makes TDD worth pursuing.

The available plugins for autotest make things all the sweeter. I personally just stick with Red/Green to make the test output easier to grok, but there’s also Growl integration for those of you who would be into that. All good stuff.

Update: Since I wrote this post, a small extension for Mac users of autotest has been released, called autotest-fsevent. This extension reduces the CPU and disk demand of autotest by using the Leopard’s FSEvent to receive notifications about modified files, instead of polling the filesystem constantly. If you ever find yourself running autotest from your laptop, this will also save you some battery time. As FSEvent is part of Leopard, autotest-fsevent requires Mac OS X 10.5.

Mocha

Mock objects are sometimes necessary, although I’m beginning to discover that they can actually do wonders for your tests if used appropriately, a realization which makes me want to go back and do some refactoring on my current project. I will perhaps cover this in a later article.

I currently use Mocha to do my mocking. It’s a powerful and comprehensive library which can set expectations, stubs, and more. However, I am not married to it, and a new contender RR seems to promise even more concise syntax and just as much power. Obviously from my keystroke-pinching comparison of Factory Girl and Machinist, I am a big fan of cutting down on keystrokes to get things done, and RR seems to be just the ticket.

TATFT?

Do I TATFT? If this means writing unit tests, functional tests, integration tests, and view tests, then no.

I am a proponent of the Skinny Controller, Fat Model pattern. All complicated business logic goes into the model, where it belongs. What’s left in the controller action is ideally no more than five lines of code (down to one or two, if possible) which have the model do something and then render an appropriate response, whether that be a redirect, a template, or a JSON object. In this scenario, writing functional tests around simple message passing seems like a waste of time when the real guts are in the model.

For this reason, I favor very comprehensive unit testing of my models. If the models are solid, then it follows that the controller code which primarily triggers the model code will likely be just fine. If there’s anything in the controller complicated enough to warrant testing, then that might be a good sign that such logic is in the wrong place.

I have considered adding view tests to complement my unit tests, especially for pages involving complex JavaScript behavior, however I’ve yet to do much research in this area.

Your thoughts?

All of this said, I think that as a community we are all still trying to strike the right balance in our approaches to testing, and I think it is still a topic worth discussing, debating, and deconstructing further. If you take issue with anything I’ve said here, I wouldn’t be surprised, and I would welcome your retort in the comments. Furthermore, I’d love to hear about any other tools which I haven’t described here, as I would hate to be missing out on some sweet test-driven nirvana.

Comments

The Web is collaborative. Isn't it great?

6af32b5bc70b13b629581509d9833e57?s=50

Chris Carpenter said:

“I shoulda been using it since a long time ago.”

Cunning.

Cd8c9864d88bcafc164d8fdb820cc451?s=50

Chris said:

So you dont write controller tests at all?

11611e595f8866809b075a8e718e7600?s=50

Chris Vincent said:

Lately I have not written controller tests because it doesn’t seem to be worth the effort for the particular project I’m on; like I said, each action in my controllers for this project is never more than a few lines long, simply triggering an action on a model and then rendering a result or redirecting. There is also some very basic exception handling going on.

However, that isn’t to say that this would always be the way to go. For example, the iPhone analytics server I worked on did have some very thorough integration tests, wherein the test simulated an API request to the server and tested the effects upon the entire stack, from the database on up to the JSON response. Since this was an API and not a user-facing web application, integration testing seemed to make the most sense and was simply the easiest way to automate testing. All of the responses were rendering JSON, not HTML, and said JSON was going to be parsed by another application written in Objective C, so we wanted to make sure the controller was rendering output that was rock solid. Of course, before deploying new code I would always bust out the iPhone client and kick the tires manually, but the vast majority of testing was through the automated integration tests.

The point I’m trying to make, which perhaps I didn’t nail head-on in the article, is that each project has different requirements and as such different testing methodologies can yield greater results for the effort; it’s all about finding that 20% testing effort which will get the project 80% of the way there, and for each project that can be different sweet spot to hit.

6af32b5bc70b13b629581509d9833e57?s=50

Chris Carpenter said:

You ever get the feeling your name is too common?

94c480bfbb8ee209e4536e1c8283a9cf?s=50

Dave Sailer said:

Thank you. This is both good information and good writing. I got here from “Rails Testing Frequently Asked Questions – The Non-Code Version”, by Noel Rappin, where you left a comment. I’m refreshing my Rails knowledge while rebuilding an existing site.

Besides having forgotten a lot of what I used to know, I see that Rails testing has taken a huge jump, and pre-built authentication subsystems have too. I’ve had to think and read a lot about both before daring to start my project. This post has helped a lot.

11611e595f8866809b075a8e718e7600?s=50

Chris Vincent said:

Testing has indeed come a long ways in the Ruby community. I’m glad my post could help you out!

21c5e2571e7cd71291bb4c23f99477de?s=50

John Lannon said:

Nice, informative article, Chris. You’re probably already aware of this, but since 1.2, factory_girl supports alternate syntaxes. If one is so inclined, one can take advantage of Machinist’s terseness. Like this:

require ‘factory_girl/syntax/blueprint’ require ‘factory_girl/syntax/sham’ require ‘factory_girl/syntax/make’

See http://robots.thoughtbot.com/post/159805892/factory-girl-1-2-adding-excitement-to-stale-factories for more.

Accaf690f3d97aab6e6e02adac8150be?s=50

Nathan Zook said:

I do think that access control should be in the controller, and reference the model only incidentally. (I disagree with active scaffold because of this.)

Say Your Peace

Textile is allowed. Be polite. Be rude. I don't really give a fuck.

Your email will be kept safe and will never be publicly displayed.

 

Wha...?

11611e595f8866809b075a8e718e7600

Chris Vincent is a 20-something drummer, producer, and engineer from the Bay Area. This is where he writes whatever the hell he wants whenever the hell he wants to write it. Check your expectations at the home page.

Obligatory tag cloud

me san francisco bicycling ruby tdd css tools iphone games rails facebooker queue facebooker facebook arduino activerecord sql css tools development process company culture spam akismet bluepill god

Recent posts

Feed me

Atom is cool.

Get in touch

Questions, comments, ideas?
Let's talk.

Unabashed self-promotion

Recommend Me