Posted: May 18, 2012
in development, general, rails, ruby
By Felipe Iketani
tags: rails, ruby, tests
For a long time, Ruby on Rails developers have put a good amount of effort into testing their apps. The Ruby on Rails community was the first to show me the beauty of automated tests.
The reason I write “Ruby on Rails community” is because I got to know Ruby through Rails, like thousands of other developers and a sickness we share is loading the whole Rails environment to application bits.
I’m not talking about the importance of developing applications entirely in Ruby and using Rails only as the gateway between the web portion and the business logic of your application, which is indeed a good idea. This kind of architecture approach was introduced to me by Uncle Bob and his really awesome screencast series from cleancoders.com.
What I am really talking about is testing our applications using plain Ruby, avoiding loading Rails environment entirely. My eyes shined when I watched the Corey Haines lecture about Fast Rails Tests. This lecture is what motivated me to write this post.
So let’s get our hands dirty.
Every Rails application has 4 important parts: Models, Controllers, Views and Routing.
Forgetting about the SOLID principles (this is not the focus of the article), let’s assume that we follow the Rails convention Fat Models and Skinny Controllers: our Models will have all the logic and we want to add a new feature.
Let’s say we have a Person model, it has a first name and last name methods and we want to concatenate them into a full name method. Pretty simple.
So, sure enough, we would start by adding specs for a method that shows the person’s full name:
Making this test pass is pretty trivial:
As a Rails developer, we know this test will take AT LEAST 8 seconds to pass and if you add more Gems, more Models etc, it will become slower and slower, and slower.
The reason this happens is that our test loads the whole Rails environment with all its gory complexity. It evens require a database to be present by loading ActiveRecord::Base. That means this is not a unit test at all, but rather an integration test.
To make matters worse, if in the future you want to change this method and include a middle name you’ll have to wait 15 seconds just for this one test to complete. It stinks. It stinks a lot.
The slowness will eventually get to you and you will want to start neglecting running the whole test suite because it takes minutes, not seconds anymore. So, when you break some unrelated specs by changing one feature in an unrelated test, you won’t notice until it’s too late. That bug went into your version control, other developers pulled and they want your head (and the one dollar fee) for breaking the build and not noticing it.
So it’s clear that we want faster tests, not only faster, but orders of magnitude faster than what we have.
Here at Webbynode we do this by isolating common behavior of classes into modules.
Let’s rewrite our example spec in a new /spec_no_rails folder using this technique:
There’s a lot of stuff going on here so let’s break it down into parts.
First, we require the naming module file, then we include it in a class created only to serve as the test subject.
Notice this class is in a plain old Ruby object and doesn’t inherit from anything, it exists with the sole purpose of including the concerns of our isolated module Naming.
Now, when describing Person::Naming, we want to use the class we just defined as our subject.
The rest of the spec is trivial and doesn’t change too much. We stub both #name and #last_name to completely isolate the behavior we want to test: that both name and last_name get concatenated.
It may seem that this is harder to test, but the benefits just scream. This test runs in around 10 milliseconds! You can run about 100 specs per second. I don’t know about you, but I find it amazing!
Making the spec pass is also very trivial. We just need to code app/models/person/naming.rb:
Finally, we write one (slow) new test for the Person class to make sure it includes our method:
That drives us to include the Naming:
And voilá, this is the fastest running unit test you will write in Rails… ever.
When you have slow tests, you start to drift away from TDD’s purpose… You may be concerned about how fragile the tests become with this approach.
Well, there’s always a trade off. You’d have to cover yourself with better integration tests, but we find this trade off does more good than harm.
We hope this simple example will engage you to try and make your Rails tests faster… For us, there was no looking back: we simply love it.
For more on the subject, be sure to check out Corey Haines’s lecture, where we first heard about this technique.