One of the primary reasons people end up being lax in letting specifications drive the development of their Rails applications is the time it takes to get feedback from running the suite of specifications. A number of tools have been built to help alleviate this pain like Spork, Zeus, and Spring. In fact Rails 4.1 will now come with Spring standard. Unfortunately, these tools are just crutches that tackle the symptoms of the problem rather than the problem itself. The actual problem is writing tightly coupled code that expects to have the full Rails framework always present, which is slow to start up.
Developing Decoupled Code
The solution is to write code that is isolated and decouple your components from as much
of the system as possible. In other words, write SOLID Rails code.
As a specific example, one might typically directly use a model class to create an instance. Instead we can use dependency injection to remove hard coded references to classes. We just
need to make sure we safely reference the defaults using either block notation or a lazy
||=. Below we have a service that needs to create Widgets which happen to
be ActiveRecord models. Instead of directly referencing the
Widget class, we use lazy evalutation in our chosen injection method. This allows us to
decouple our code and not need ActiveRecord loaded.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
A Base Rails-free Configuration
When writing your applications in this way you can then start to restructure how you setup your specifications and minimize the required environment to run both your specification and your code fulfilling the specification. The
spec_helper.rb will have a line like this:
This is what loads your entire Rails application and slows down the running of your tests.
To make your specifications faster, you need to use a configuration file that does not contain
this line. So let’s start by creating a very light weight
1 2 3 4 5 6 7 8 9 10 11 12 13 14
We are requiring
active_support/dependencies so we can have access to the autoloader Rails uses without actually loading up all of Rails. It is fairly light weight and the convienence outweighs the cost. In each spec helper which requires this base we will add the relevant portions of our app into the
Plain Ruby Object Specifications
Depending on the part of application that you are specifying, you can create spec helpers
specific to what you need in any one context. For example, the simplest would be one for
specifying any type of pure Ruby class such as a service class.
services_spec_helper.rb might be:
1 2 3
For your decorators, you might choose to use Draper and your
decorators_spec_helper.rb might look like:
1 2 3 4 5
Testing models needs a little bit more. Assuming you are using ActiveRecord you’ll need to include that as well as
establish a connection to your database. We won’t include
database_cleaner as most of your tests
should not be actually creating database objects. In fact, the only place you really need to actually create an object in the database is when testing uniqueness validations. When you do need to create something you can just manually
clean it up or use a transaction. So a sample
models_spec_helper.rb can look like this:
1 2 3 4 5 6 7 8 9 10 11 12
Finally, when creating feature specs, we do need our full Rails stack and our
feature_spec_helper.rb is going to look very similar to what your current
spec_helper.rb looks like.
I found myself using varitions on the above spec helpers in projects I work on and decided I would write a set of generators to make it easier to bootstrap the project. The gem can be found at https://github.com/Originate/rails_spec_harness
While introducing these changes into existing projects I have found speed increases of 8-12 times. The worst project experienced a 27x increase once these changes and the corresponding changes in coding habits where applied. As an example
I made a specification with 4 examples for a plain Ruby class. I then used the
time command line utility to measure running rspec with the minimal spec helper as well as the full Rails spec helper and found the following:
|Spec Helper||Real||User||Sys||RSpec Reported|
Write SOLID code, isolate your specifications and enjoy a fun and sane development experience.