Posted: May 18, 2012
in development, general, rails, ruby

By Felipe Iketani
tags: , ,

A faster way of unit testing in Ruby on Rails


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.

Webbynode is Web Application Hosting for Developers Lean more .

Leave your thoughts

  • http://www.facebook.com/michael.leveton Michael Leveton

    nice article guys.  You should consider changing the title to ‘much faster unit testing with rails.’  Because testing with rails is old news but speedy rails testing is cool. 

  • jrochkind

    The default rails test_helper testing setup will load the Rails env whether you need it or not, right?

    How do you set things up so it won’t load it unless it’s running a test that actually needs it?

  • Felipe Iketani

    Hi !
    well.. if you run 1.000 unit specs and at least 1 test with test_helper (or spec_helper) you WILL load Rails,

    but if you run only tests that doesn’t require these helpers, you will be fine.

    we separate usual specs from the specs folder from the fast spec, which are in he spec_no_rails folder.

  • http://geoff.evason.name Geoff Evason

    In exchange running a single test faster you’re making the code far less readable. Seems like a bad idea.

  • http://www.derekhammer.com/ Derek Hammer

    I agree that fast tests are desirable and that we should work toward them. I am just not sure that breaking Fat Models into modules is a good approach. For example, this module is assuming that you have a ‘self.first_name’ (why not just ‘first_name’?) and ‘self.last_name’ wherever it is being mixed in to. Add more and more dependencies and now you’ve really coupled your code to the model and made your application *more* brittle (despite the fast tests).

    This is really just the ‘poor mans Repository/Service’ abstraction that abuses modules to make our tests fast. Instead, we could follow the other advice in your article (to write Ruby instead of Rails) which would give us the benefit of fast tests but none of the downsides of module abuse.

  • http://www.spry-soft.com David White

    I agree with Derek, re-structuring an application shouldn’t be done to speed up the test suites. If you have a logical reasons to split functionality out of the parent model, perhaps a series of methods that could be grouped together and reused within a separate model then that’s a different matter.

    There is also a very good gem called spork for keeping you test environment running in the background, speeds up tests no end.

  • http://www.derekhammer.com/ Derek Hammer

    Don’t think I agree with that statement. Slow tests = hard to test = poor design.

  • Anonymous

    That’s debatable. We are using includes for very specialized and common responsibilities. 

    We found out that leaving database-related methods on the model itself, and using modules for things that are common makes the code a lot clearer.

    There are two setbacks though: one is that, in order to find a method, you have to look more places. But it hasn’t been a real issue so far.

    The second setback is that it can cause brittle tests. For instance, you can declare a new module that should be included by a model, and you can forget to include it. If you run the module spec alone, it will pass, but that behavior never made it to the object. That’s why you must have better integration tests, they will cover you a**.

  • Anonymous

    The Repository/Service abstraction is really interesting when you have a clean state to start with.

    This application is being actively developed for the last year and our specs started to become slower and slower every day, to a point that we were really neglecting run it outside of our CI server because it was horribly slow.

    We were aware about the abstraction, however we did not want to perform a major refactoring of the application. What we did is we extracted common behavior into separate, more specialized modules and started testing them in isolation. It worked beautifully for us from the beginning and it make running our specs about 100 times faster by avoiding the load of Rails environment. 

    In the end, we achieved that with almost no change on the code, which is why we decided to write about it. You can take an existing Rails application with horrible slow specs, and start extracting behavior into specialized, easy to test modules without performing too much architectural change.

    In the end, the path you choose is up to you and your team. Given what we had, the effort it took, and the end result, we are very, very satisfied. Is it the most beautiful architecture out there? Hell no, but I think using SOLID principles for Rails application is a trend that is starting to catch up nowadays while there is a ton of existing applications built around the “Railsy” MVC pattern that can benefit from this technique and that’s why we found worth sharing.

  • Anonymous

    David, we’ve been there. Spork caused all sorts of problems to our application and even though there were speed improvements, they weren’t near what we have with testing pure Ruby objects.

    On top of that, you still have to reload the Rails environment in some specific cases I can’t remember now. 

    For our team, getting Spork to play nice and the speedup it brought wasn’t worth it. We love our approach and we’re definitely sticking to it.

  • http://www.spry-soft.com David White

    I agree slow tests are usually the result of poor design but cant see splitting out functionality that belongs in your model into a module that’s coupled to your model just to make your tests quicker as the solution.

    Plus the main reasons for rails tests being slow that I’ve come across tend to be the initial load of the application into memory, but the majority of Rails apps I’ve worked on have been quite small.

  • Anonymous

    I like the idea, but think a few changes would result in a better implementation:

    `require “./app/models/person/naming”` The relative require here breaks if you’re not in the root dir. A better way would be to require spec_helper (which would be spec_no_rails/spec_helper.rb) and then add/models to the load path in there.

    Here are some suggestions that I think would make it better:

    Instead of having to stub out all the methods your module expects, on instances of PersonForNaming, make PersonForNaming a Struct with those methods. Then they’re real methods that all instances share, but they’re still trivially easy to implement. Notice that right now, with no methods on this class, it is useless. You could do `subject { Object.new.extend Person::Naming }` and get the same as you currently have.

    You could create some official way of verifying wiring. Right now these tests pass even if Person doesn’t have a first_name and last_name. But if you had the modules declare what methods they were expecting, then you could make a matcher that checked that instances of your class would respond to these methods.

    The `its` keyword is going to be removed from RSpec in the future (source)

    Here is a gist showing these changes.

    Also, it might not be wise to namespace Naming under Person, because Person is an ActiveRecord model, so you can’t talk about Naming without also talking about ActiveRecord (and if you don’t specify ActiveRecord::Base as a superclass the first time you load Naming, then you can’t ever pull it in afterwards)

  • http://twitter.com/shanehanna Shane Hanna

    Your point about separating your model/logic from the ORM is fine but the mock objects drive me nuts, I don’t know why people use them.

    Even in this simple example your mocks know too much about the internal workings of full_name. Specifically you need to know which methods full_name calls. If I changed how full_name worked, say by adding a middle name I’d not only have to update the expected output but also update the stubs to work with my new implementation of full_name. Even worse if I added a method call inside full_name that doesn’t modify how the method functions (say a logging call) I still have to add this method stub or the mock will fail and my tests will break for no good reason.

    I’ve just stepped into a new role and I’ve found blocks of 10+ lines of stub code where a single line ‘real’ object would have worked just fine. I spend most of my days updating mock code to match the internals of methods that should be black boxes all to get around the fact Rails3 (not Ruby) is a slow piece of shit and a poor choice just about always.

  • Anonymous

    > Your point about separating your model/logic from the 
    > ORM is fine but the mock objects drive me nuts, I don’t 
    > know why people use them.

    And I don’t know why people are so afraid of using them. They express intent and dependencies better than anything else in my opinio.

    I also think the stubs in this case make the test a lot clearer. It clear shows that this particular unit test given a first_name and a last_name, the following output is required. 

    If you change the implementation to require a middle_name, you would have to change either your fake object or the stubs you add to the object, so I don’t see any drawback in having the stub way.

    > Even worse if I added a method call inside full_name that 
    > doesn’t modify how the method functions (say a logging call) 
    > I still have to add this method stub or the mock will fail 
    > and my tests will break for no good reason.

    You’re not thinking this the right way. To add a new log call to your method, you’d have to TDD it, and not just add a simple log call.

    Using our approach, I would expect a new spec to be written in to the same context BEFORE adding the logging call, something like this:

    https://gist.github.com/b38aaf0103227aa10467

    Unit tests are not black boxes, they are testing the insides of your object, also testing expectations of interactions from your object to the outside world.

    > I’ve just stepped into a new role and I’ve found blocks of 10+ 
    > lines of stub code where a single line ‘real’ object would have 
    > worked just fine. 

    Again, you’re missing the point. It’s not about whether the real object works or not. It’s about isolating dependencies — even if those dependencies are the own object. I disagree that having the own object is better. Some times having the own object makes you have a huge setup, loading a huge amount of dependencies that you don’t care about when testing a single unit of code — a method for instance.

    > I spend most of my days updating mock code to match the internals 
    > of methods that should be black boxes all to get around the fact 
    > Rails3 (not Ruby) is a slow piece of shit and a poor choice just 
    > about always.

    Great attitude. Rails is a slow piece of shit FOR TESTING purposes. And I am glad there are ways around it. Poor application design is what is a piece of shit in my opinion.

    Now if your mock code is getting so distant that they don’t match internals, you’re doing something wrong. And if that’s happening too ofter it is a clear sign that you or your team is either not using TDD at all and/or not running tests all the time as they should.

  • Anonymous

    Guys, just a tip: using Unit tests without Contract tests in some places is going to give you false positives later because of the doubles.

    I love using Contract tests and I think you should give it a try too.

    You should definitely watch this video: http://www.infoq.com/presentations/integration-tests-scam (he addresses Contract tests past the middle of the video)

blog comments powered by Disqus