If you wish to travel far and fast, travel light - Cesar Pavese
At Abletech, we love tests. After all, an application is only as good as its tests. And for the most part tests love us, but sometimes they love us a little too much.
No matter how much you optimise your tests, when a project or application gets to a certain size or scope, a comprehensive test suite can get a little unwieldy to run as a whole. For example, the last three applications I’ve worked on have had multiple thousands of unit and integration tests each, which if run locally end-to-end for one of these apps could last up to 50 minutes!
Most of the time though, it’s easy enough to limit the tests you’re running to a specific area that you’re working on e.g. with Rspec, limiting the test run to either a specific file rspec ./spec/controllers/users_controller_spec.rb
or directory rspec ./spec/models
.
However, when the full test suite is too slow to run in its entirety, but your code is scattered over the standard convention-over-configuration codebase, a developer is often faced with having to run each test file individually — which can be painful, tedious and leave a developer open to accidentally omitting a test from any given test run. Not good for productivity, frustration levels or accuracy.
In this most recent case, we were working on a new API for a client to service their upcoming mobile apps where we had dozens of files spread over each of the controllers, models, serializers, use_cases and jobs directories.
What can a developer do to fix this? If in doubt, script it!
While convention-over-configuration undoubtably made it difficult to test due to the spread of code, it also can come to your aid when applied well. In this case, as our intent was keep the API code and logic separate from the existing logic in the web application, we had namespaced the code under the existing directories (controllers/ etc) with /api/mobile/[version].
We had a discernible pattern we could recognise - /[directory]/api/mobile
- just not one that Rspec could automatically work with.
So, with that namespace pattern, we were able to use the *Rspec *RakeTask
to whip up a test suite specifically for this API project that could not only comprehensively test all the code we were working on in mere seconds but also provide us with piece-of-mind that nothing could get left out.
Here’s the code (lib/tasks/spec_suites.rake):
require 'rspec/core/rake_task'
namespace :spec do
namespace :suite do
desc "Run all specs in Mobile APIs spec suite"
RSpec::Core::RakeTask.new(:mobile) do |t|
t.rspec_opts = ['--options', "#{Rails.root}/spec/spec.opts"]
t.pattern = 'spec/*/api/mobile{,/*/**}/*_spec.rb'
end
end
end
Note the rspec_opts
picking up the existing Rspec options, and the non-recursive/recursive directory reference in the pattern
.
And to run this is as simple as runningrake spec:suite:mobile
(spec:suites
being the namespaces that conform to the file name and mobile
being the actual task name.)
Bear in mind that you’ll want to exclude this from being loaded into any environment that Rspec isn’t loaded in (e.g. everything except test and development normally) by either excluding it in what ever task loading process you have or surrounding the task in an environment check.
So, in summary — tests are good; convention is good; simplicity is good; it’s all good.