Posted by Nick Sieger Mon, 11 Jun 2007 15:35:00 GMT
Many libraries and plugins ship custom Rake tasks. Of course, as slick as Rake is for a build and configuration language, it’s still just Ruby code right?
Case in point: I released a version of
ci_reporter with a fairly careless bug in a rake task that attempted to
<< a string into an existing environment variable. It escaped me at the time that Ruby sets up the
ENV hash with frozen strings, because my own usage of ci_reporter did not exercise the task in that way.
So shouldn’t that Ruby code be subjected to the rigor of automated testing just like the rest of your code? It became obvious to me that it must be so. It turns out it’s straightforward to use Rake in an embedded fashion, and invoke targeted tasks in your custom Rake recipes. The examples here use RSpec, since that’s what I use for testing
ci_reporter, but you could apply this to
Test::Unit as well.
The technique is to create a new instance of
Rake::Application, make it the active application, and load your rake scripts into it:
describe "ci_reporter ci:setup:testunit task" do before(:each) do @rake = Rake::Application.new Rake.application = @rake load CI_REPORTER_LIB + '/ci/reporter/rake/test_unit.rb' end after(:each) do Rake.application = nil end # ... end
Notice the use of
#load rather than
#require, as you want to execute your rake script each time you setup the Rake application object. When tearing down your test or example, you should cleanup Rake by setting the
Rake.application back to nil (or save the previous application and restore it, if you prefer).
Now, in the body of your test or example, you invoke your rake task with
@rake['target'].invoke. Here, I’m exercising the case of an existing, frozen
ENV value. After the task is invoked, I check the value after the task to make sure the variable was modified as expected.
it "should append to ENV['TESTOPTS'] if it already contains a value" do ENV["TESTOPTS"] = "somevalue".freeze @rake["ci:setup:testunit"].invoke ENV["TESTOPTS"].should =~ /somevalue.*test_unit_loader/ end
I was fortunate here that the tasks for which I wrote tests after the fact were simple enough to be testable on their own, which may not always be the case, especially with organic, homegrown Rake tasks that interact with the world outside of Ruby. Still, if your Rake tasks are a critical part of your application, library or plugin, they should be tested. For example, it would be nice if tests could be written for the Rake scripts in Rails’ Railties module to increase coverage there.
Perhaps someone out there will run with this idea and take up the challenge and write a Rakefile completely in a test-driven or behaviour-driven style. It’s always been a sore point for me with Make, Ant, Maven, and virtually every other build tool in existence that you have no other way of automatically verifying your build script is doing what you intended without manually running it and inspecting its output -- it just feels so dirty! I’d expect that test-driven Rake scripts would likely have the level of granularity to match the tasks that need to be done, in a way that you can combine them in the right ways to make incremental and deconstructed builds simpler.