CONTENTS OF THIS SITE

OUR OTHER CONTENTS

RECENT BLOG ENTRIES

Migrating from Test::Unit to RSpec

January 4th, 2008 by comment jacqui

At Streeteasy, nearly half of our tests are still written in Test::Unit, so it’s hard to see what our actual test coverage is using Rcov.

I read recently that RSpec got support for Test::Unit interoperability. Obviously now is the time to make the switch from Test::Unit to RSpec. You can do it without a mass exodus from Test::Unit. Use your existing tests inside the RSpec test harness.

So here’s how I converted all out legacy tests to rspec.

Step One: update RSpec

This is fairly self-explanatory and written up elsewhere. However, in short, I updated both our rspec and rspec_on_rails plugins, remembering to rerun

$ ruby script/generate rspec

Make a back-up copy of the spec_helper if you’ve customized it. Compare it to the one generated for the new rspec version to see if there’s been any significant changes, and if so, merge them into your helper.

Step Two: move your files from test/ to spec/

This is what I did: I copied test/unit/* to spec/models/, renaming them appropriately:

  ## current_path is a hash where :tu => test::unit path and :s => spec path
  def make_tests_specs(current_path)
    current_path[:tu].each do |file|
      unless file == “.” or file == “..”
        full_file = File.join(current_path[:tu].path, file)
        if File.directory? full_file
          spec_file = full_file.gsub(/test/, “spec”)
          spec_file.gsub!(/unit/, “models”)
          spec_file.gsub!(/functional/, “controllers”)
          FileUtils.mkdir_p(spec_file) unless File.exists? spec_file
          make_tests_specs({:tu => Dir.new(full_file), :s => Dir.new(spec_file)})
        else
          new_spec = File.join(current_path[:s].path, file.gsub(/_test/, “_spec”))
          puts “converting TestCase #{full_file} to ExampleGroup #{new_spec}\n”
          File.copy(full_file, new_spec)
        end
      end
    end
  end

Step Three: change your assertions

While that would move all my tests over to the specs directory and rename them, I figured, why should I stop there? I realized I could probably do a lot of the Test::Unit to RSpec syntax conversions programmatically. Here are the regular expressions I used to do the substitutions - while these worked great for our codebase at StreetEasy, your mileage may vary, so be sure to use caution before running this against your code base (and hey, you do use source control, don’t you?):

if line =~ %r@require.*?test_helper@
  new_file.puts "require '#{RAILS_ROOT}/test/test_helper'"
  new_file.puts "require '#{RAILS_ROOT}/spec/spec_helper'"
elsif line =~ %r@class.*?TestCase@ and line !~ %r@Controller@
  new_file.puts "describe 'transitioning from TestCase to ExampleGroup' do"
elsif line =~ %r@class\s*(.*?)Test@
  new_file.puts "describe #{$1}, 'transitioning from TestCase to ExampleGroup' do"
  new_file.puts "integrate_views"
elsif line =~ %r@def setup@
  new_file.puts "before do"
elsif line =~ %r@def test_(.*?)\n@
  new_file.puts "it \"should #{$1.humanize.downcase}\" do"
elsif line =~ %r@assert_response :success@
  new_file.puts "response.should be_success"
elsif line =~ %r@assert_response :redirect@
  new_file.puts "response.should be_redirect"
elsif line =~ %r@assert_redirected_to (.*?)\n@
  new_file.puts "response.should redirect_to(#{$1})"
elsif line =~ %r@assert_equal (.*?),\s(.*?)\n@
  m1 = $1
  m2 = $2
  unless m1.nil? or m2.nil?
    unless m1.match(/nil/) or m2.match(/nil/)
      new_file.puts "#{m1}.should == #{m2}"
    end
  end
elsif line =~ %r@assert assigns\(:(.*?)\)$@
  new_file.puts "assigns[:#{$1}].should be_true”
else
  unless line =~ /^class.*?Controller.*?rescue_action.*?end$/i or line =~ /require ‘.*?_controller’/i or line =~ /^#/
    new_file.puts line
  end
end

The above block of code will convert any response (:success, :redirect, redirect_to), assigns, and equality assertions. It will change your method declarations to “it ’should…’ do/end” block syntax. It will require both the test_helper and spec_helper. It will integrate_views by default on your functional (controller) tests; you might want to go through those by hand later and separate out the front-end stuff into a set of view tests, depending on how you feel about them.

You could definitely write more substitutions - there are more assertions in Test::Unit, of course. If your code base makes extensive use of other assertions you’ll probably want to add to the regexes above. In our case, however, I found the above satisfied converting the most straight-forward Test::Unit assertions, leaving me with the slightly more complicated examples to convert by hand, which brings me to the next step.

FYI, you can find a table of Test::Unit assertions and their RSpec equivalents here on the RSpec documentation site.

Step Four: run rake:spec and see what happens

You might as well see what situation any automated conversion process left you in. I had a few errors during this phase - mostly due to the fact that I forgot to move my fixtures, woops - but it didn’t take me very long to resolve them.

Step Five: go through each sparkling new spec and convert any remaining Test::Unit-based assertions into RSpec syntax.

The good news: you can take your time on this step since RSpec now plays nice with Test::Unit. I was in a particularly motivated and productive mood after finding myself with a big directory full of specs (and an amazingly empty test/ directory) so I just went through them all right then and there. It took me most of the afternoon, but when I left work that day I was able to see this, which is just awesome:


$ rake spec
........................................................................................................................................................................................................................................................................................................................................................................................................................................................

Finished in 70.023361 seconds

440 examples, 0 failures

As you can see from the amount of time it took all 440 examples to run, we could definitely benefit from a greater use of mocks and stubs in our controller tests. 70 seconds is a tad bit long. That’s next on the to do list - mock and stub wherever I can in our controller tests, followed by integration/big picture testing using Story Runner, and then, if there’s time*, refactor our specs to be better organized and efficient - I can think of several places where I had wished for nested examples, or could have used shared examples, had I been aware they existed at the time.

I’ll be writing up my experience with Story Runner in the next couple of weeks. Til then, though, best of luck in your specs!

* always a compromise when you’re trying to do things the right way and add business value simultaneously.

For more information Behavior Driven Development, check out Dan North’s article, “Introducing BDD.”

You can find the documentation for RSpec at http://rspec.info.

Many thanks to Joshua Sierles, Paul Marsh, and Josh Knowles for their time in reviewing this!

ˆ Back to top

RubyEast Recap, Slides, and Other Thoughts

September 30th, 2007 by comment desi

I spoke at RubyEast this past Friday and I think the presentation went pretty well. It was my first presentation in a speaker/audience type setting so I was very nervous. I have presented at Agile 2006 but it was a game (interactive) and was co-presented by several other people. This presentation was the first time I stood in front of a room full of people and spoke and everything went very well. Like I said I was really nervous but as soon as I got started the nervousness went away. I think I am very lucky because I was able to present to a room full of very nice/cool people and that made the experience a great one. I want to actually thank the people who came to hear me present and who gave me great feedback and encouragement afterwards it really made my day. If you are interested here are the slides for the presentation. A Tour Of Rails Testing using RSpec

I didn’t get to see many of the sessions because I was busy preparing for my talk but I was able to catch Obie’s presentation - Advanced ActiveRecord which was really good (and I am not just saying that because he is my boyfriend). I also caught the ending Keynote where Nap (I actually don’t know his real name) announced the Rails Rumble winners. There were several screencasts and it made me wish that Obie, Clay, Nick and I would have had time to get the video that was shot of us over the weekend edited and ready for prime time. We had a blast doing the competition and while we didn’t win (we got honorable mention) we learned a lot and I think we all grew closer in those 48 hours. The teams that did win did a tremendous job on their apps and well deserved the loot. Take a look at the winners there really are some great apps. Rails Rumble Winners

Friday evening a bunch of people got together after the conference and played several games of Werewolf which is a really fun game to play. I got to know a lot of people during that game and it was a great way to wind down.

Couple of other thoughts before I end the post. ShesGeeky (un)Conference sounds like it is going to kick major ass so any of you ladies out there who can attend make sure you get registered. Additionally, ladies if you want to talk during the conference please contact the organizers.

GrrrlCamp seems to be getting a good footing. I was lucky enough to meet THE Gloria this past Friday and I look forward to being a part of GrrlCamp.

I have taken on an apprentice and she will soon be posting to the blog about her experiences. I am in the process of trying to see if creating an apprenticeship type program run by DevChix is possible because after speaking with Sonia (one of the women on DevChix) she helped me figure out that I would really like to have a program that fits the apprenticeship model rather than a mentoring program. Look for more to come on this in the future.

ˆ Back to top