When working on Ruby on Rails apps, slow auto-tests can be the bane of the developer’s life. For complex applications, the test suite can take up to ten seconds to load up the entire Rails stack. This may not sound much, but it soon adds up when tests are running constantly. When following Test Driven Development (TDD), you need to ensure there is a fast feedback loop (red-green-refactor cycle) between writing the test and making changes to the code. At Bit Zesty, we’re passionate about the possibilities of TDD and wanted to find a way to speed up the process.
“preload Rails so that your test suite (specs) will run immediately when launched.”
While there are architectural ways to improving your test speeds, such as the hexagonal Rails approach: https://blog.mattwynne.net/2012/04/09/hexagonal-rails-introduction/, they are relatively new and not fully-formed best practises yet. They also can’t help with existing applications.
However, there is another solution: preload Rails so that your test suite (specs) will run immediately when launched. There are a few tools out there that can help.
Zeus preloads your Rails app so that your normal development tasks – such as console, server, generate, and specs/tests – take less than one second.
Spring is a Rails application preloader which launches on first use and, unlike Zeus, does not require starting the server.
irb-config allows you to stay in the Ruby interactive shell and run RSpec/Cucumber tests directly in the rails console.
Each of these has different benefits and downfalls, depending on the qualities you’re looking for. To discover which tool is best for which purpose, we examined each in terms of code reloading, speed, and spec execution.
Reloads models correctly when modifying callbacks and scopes, which is not always the case for servers with hot-reload, such as Unicorn. It also reloads views when writing integration specs.
Reloads the code in the same way as Zeus, allowing more dynamic configuration for spec_helper loading.
Does not reload models correctly when modifying callbacks and scopes. The code is reloaded on top of what is already loaded. If you notice odd behaviour, just kill and reload the console.
Does not reload views files, as those are not Ruby files and console has no way of reloading them.
Takes a few seconds to reload after modifying the model (extending AR) class file. This is annoying for TDD, as you usually run the test just after file modification. However, there is one interesting observation – Zeus reloads fast if you are not modifying a model class (e.g. service).
Performs the same as Zeus. It takes a while to load the Spring server on first command launch as it gets preloaded only when used.
Reloads modified classes much faster than the alternatives. You need to manually reload the modified file – use vim bindings or load “file_name” command after each file modification.
Does not support launching specs focused to a block, such as:
zeus test spec/models/user_spec.rb:12
After executing specs, Zeus seems to try to launch an empty spec file for some projects. This results in strange output to the console but it disappears after the normal specs output and can be ignored.
Using fork works fine most of the time. However there are edge cases and it is not 100% reliable. If you notice odd behaviour, just kill and reload Zeus.
Runs the specs well, but takes as long to load and run as Zeus. It uses Rails built-in code reloader.
Despite the limitations of code reloading, it runs the specs fine. It consumes few resources, as it does not do any forking. However it fails to run the specs for some of our projects.
You can combine these tools in different use cases to get the best of speed and agility.
Use Zeus or Spring when writing integration specs, as irb-config will not reload the view files. For model specs, I tend to use irb-config as it reloads the Ruby files and gives feedback much more quickly.
Even when using these tools, there are still issues with code reloading. The only way to mitigate these is by architecturing your application using the following principles:
- Avoid non-trivial callbacks by using the domain events as described by https://www.confreaks.com/videos/759-rubymidwest2011-keynote-architecture-the-lost-years. This will also save you from shooting yourself in the foot when the application grows bigger.
- Break the classes down into smaller pieces. This leads to skinny models, skinny controllers, as well as clearly separated responsibilities.
By combining these practices with the use of tools to preload Rails, you can greatly speed up the process of auto-testing in TDD.