Fast Rails Tests are a Half-Hearted Effort

There is a niggling problem with fast rails tests. Actually, it’s kind of a fundamental problem. If you are testing code that doesn’t need rails, why is that code in your rails project? If it is, you’re feeling the power of the dark side.

Getting fast rails tests requires some effort. You need a separate directory. Those tests may have a separate spec helper. You have to prevent them from loading the default spec helper. You need to tell all your tools which directory to use, and slap them upside the head when they make assumptions. Some will ignore you anyway. The fact is that all this is swimming upstream – we already have a well established method for making completely independent tests.

“Rails is delivery mechanism, not an architecture.” – Uncle Bob Martin, The Lost Years of Architecture

Uncle Bob has a solution for monolithic Rails projects: break them up into separate gems. The really nice thing about this is that gems have their own tests. Now you are swimming downstream – going with the flow and working the way the tools expect to be used. It’s a lot harder to accidentally load Rails, and if you by chance use a railism, the tests will just fail. (Given sufficient coverage.)

Fast rails tests are a half-hearted effort because it only separates the tests. If the code you are testing is really independent of Rails, a whole-hearted effort would lift the code itself, along with it’s tests, into it’s own gem (“I’m a real boy now!”) Since I don’t need to share these gems between projects, I’ve been putting them into /vendor/modules. I shied away from other directories like /vendor/gems because I didn’t want other tools making assumptions about where those gems came from or what it is or isn’t safe to do with them. Everything still exists within one source control repository, but I’ve got my eye on a future where the modules become full fledged gems shared between multiple projects.

Separate gems save you from jumping through hoops to run your tests. There’s just one little problem: now you have to jump through hoops to run your application.

“Once you start down the dark path, forever will it dominate your destiny” – Yoda

Rails wants to own your code and eat your soul. It wants total control of your app and where it’s files are located. If you move the cheese, Rails will get all pouty, pee on the carpet, and dig up the flowerbeds.

One of the conveniences Rails provides is automatic code reloading. It’s the kind of thing you don’t notice until it’s gone. Rails only provides automatic code reloading for user files it owns. If it’s coming from “a gem”… well son, that there’s library code, which will obviously never change. So it doesn’t need automatic code reloading, which would after all, just be overhead.

The alternate is, just likely every other Ruby application, to stop the program and start it from scratch to see your changes. In case you haven’t noticed, Rails takes a long time to start up. It’s a long way from fast Rails tests. One defense, of course, is to run your fast non-rails tests and try to get everything working before integration. In the real world, we still have integration problems, which means you’ll still be cycling the Rails server.

The appropriate levers to fix this are ActiveSupport::Dependencies.autoload_paths and ActiveSupport::Dependencies.explicitly_unloadable_constants. However, I still run into problems and haven’t yet done a sufficient deep-dive on the problem to really understand how to properly set things up.

Posted Friday, December 16th, 2011 under Essay.

One comment so far