On our Ruby on Rails project we use TextMate. Run Focused Test is one of our favourite features. However we have some tests that are modules. e.g. we have
class CatTest < Test::Unit::TestCase include AnimalTests ... and then some Cat-specific tests end class DogTest < Test::Unit::TestCase include AnimalTests ... and then some Dog-specific tests end
One annoying thing is that after we wrote a test in AnimalTests, we cannot run it quickly in Textmate, because we can’t do a Run Focused Test in AnimalTests. So Shane Harvie and I decided to work on a Textmate bundle, so that we can be in AnimalTests#test_foo, hit Alt-Cmd-M, and it runs test_foo for us in the context of both CatTest and DogTest.
It does so by:
- searching through all the files in the same directory (that’s the convention of our current project: they reside on the same directory)
- pick out the files that “include AnimalTests” (i.e. cat_test.rb and dog_test.rb)
- do a “ruby cat_test.rb -n test_foo” and a “ruby dog_test.rb -n test_foo”
- output the same nicely formatted test output, through using the Rubymate bundle run_script.rb
One tricky thing is that in run_script.rb
class RubyScript < UserScript
(the class UserScript is defined in scriptmate.rb as part of the Textmate bundle framework)
There’s some undesired behaviour that we need to override on UserScript#initialize. However, run_script.rb also executes the Class after defining it, so by requiring the file we’ll have already executed the undesired behaviour. We didn’t want to modify RubyMate’s run_script.rb because that’ll mean maintaining one extra file, and we’d like to keep everything as one bundle for easy distribution. So how did we get around this? Invoking our Ruby interpreter using -e.
TextMate Bundles are basically Shell script, but many of them, like RubyMate’s Run Focused Test, are partly written in Ruby and have the result returned into a shell script variable.
for file_name in ${base_files[@]}
do
"${TM_RUBY:-ruby}" -e '
require "#{ENV["TM_SUPPORT_PATH"]}/lib/scriptmate"
...
class UserScript
def initialize(content, write_content_to_stdin=true)
...
end
end
ARGV[0]=RunFocusedTestInModule::Parser.new.parse
require "run_script"
'
done
So in the above code snippet,
$base_files is the shell script array variable, and the do and done are shell script, but in between it’s running the Ruby interpreter with a -e. So, in there we require scriptmate.rb (thereby loading the UserScript class), then override UserScript#initialize, then require Rubymate’s run_script.rb. That seems to do the trick.
Hey Ricky,
How are ya? I’m curious what the motivation to retest base class behavior when you’re testing a class.
Thanks! Take care.
-Prasanna
Hey Prasanna! I didn\’t describe it well, but the main reason is because it\’s a base module, not a base class:
class Cat < ActiveRecord::Base; include Animal; end
We have the Animal module that contains common behaviour between Cats and Dogs, and we tested the Animal module not directly, but through testing Cat and Dog.
An alternative will be to have a test suite for the Animal module:
class AnimalTest < Test::Unit::TestCase
and have a test animal that includes Animal:
class TestAnimal < ActiveRecord::Base; include Animal
and test Animal that way. It just turns out that in our project, on the Animal module there are many template methods and so it\’s easier to test the subclasses rather than the module.