I'm going to write my process turning the Test::Unit tests into RSpec examples for another Rails app in realtime. The app is called "YAGS" (Yet Another Genetics Simulatr), for simulating the genetics of fruit flies for a college-level biology course. I did this transformation once before; this app is a little bit more than a CMS for my former CS department.
My post for the department CMS transformation was organized topically. I'm organizing this one chronologically.
Measure the State of the App
# unit tests
124 tests, 758 assertions, 0 failures, 0 errors
Test suite finished: 5.091451 seconds
# functional tests
164 tests, 1311 assertions, 0 failures, 0 errors
Test suite finished: 6.700129 seconds
# integration tests?! we don't need no stinkin' integration tests!
Install RSpec and RSpec-Rails in the App
While I'm at it, I think I'll also get Cucumber set up. (The last time in my "state of the app" is a lie; everyone needs integration tests!)
I drop these into my config/environments/test.rb file:
config.gem "rspec", :lib => false, :version => ">= 1.2.7"
config.gem "rspec-rails", :lib => false, :version => ">= 1.2.7"
config.gem "aslakhellesoy-cucumber", :lib => "cucumber", :version => ">= 0.3.11"
config.gem 'webrat'
These may be useful for a limited time only (especially getting cucumber from GitHub). I run these shell commands:
# I don't bother to install since I know they're installed
rake gems:unpack RAILS_ENV=test
rake gems:unpack:dependencies RAILS_ENV=test
./script/generate rspec
./script/generate cucumber
Question for Rails experts: with the gems tasks (like install and unpack), is it necessary or even desirable to add the gems to version control? Can't you have your deploy script do the installing and unpacking? I'd really appreciate feedback on this.
I run the spec and features tasks, and they both run without errors. Cucumber at least reports "0 scenarios, 0 steps"; RSpec is mute.
Copying Tests Over
mkdir spec/models
mkdir spec/controllers
mkdir spec/views
mkdir spec/fixtures
cp test/unit/*.rb spec/models/
cp test/functional/*.rb spec/controllers/
cp test/fixtures/*.yml spec/fixtures/
I have to make the directories because I haven't generated any examples for RSpec directly. When I finish with these steps, I discover why the spec task didn't seem to do anything: it didn't find any specs because the filenames of specs end in _spec.rb, not _test.rb. I wish I could give a command-line solution for this, but I haven't been able to find a way to get mmv (multiple mv) installed on my Mac. Coincidentally, I just discovered NameChanger today, and it does a very nice job at renaming the files.
I suppose this might work:
# run in spec/models, spec/controllers
for i in *_test.rb; do
mv $i ${i%%_test.rb}_spec.rb
done
(After I finished the whole Test::Unit-to-RSpec transformation, I discovered that mmv is available in MacPorts! The name of the packge is mmv. I'm too lazy now to figure out what the right mmv command would be, but it's not to hard to figure out.)
Can't Find Test Helpers
no such file to load -- ./spec/controllers/../test_helper (LoadError)
That's because they're spec helpers now!
I use RubyMine's "Search > Replace in Path..." to replace the test_helper requires with spec_helper requires.
I know the next problem: helpers that I haven't copied over to spec_helper!
nokogiri Gem Acting Up
Two minutes later... I'm wrong, of course! I'm not having troubles with the helper methods yet. I'm having troubles with nokogiri and/or webrat. The basic error message is this:
no such file to load -- nokogiri/nokogiri
The spec task also spits out a lengthy stack trace and concludes that webrat isn't installed. Well, newsflash! webrat is installed, and so is nokogiri. But there isn't a lib/nokogiri/nokogiri.rb file anywhere that makes sense out of the error message. Even more frustrating, my other Rails app isn't having a problem with webrat and nokogiri
There is, however, a difference between the two apps: I do not have webrat and nokogiri in vendor/gems in the CMS app. They have been unpacked in the YAGS app.
rm -rf vendor/gems/webrat-0.4.4
rm -rf vendor/gems/nokogiri-1.3.2
Problem solved. I won't say I'm happy with the solution since it begs the question why it works, but webrat (and Cucumber) have been in enough flux lately that I don't worry if I have to do something out of the ordinary to get them to work for a while. As far as "out of the ordinary" goes, deleting the gems from vendor/gems isn't going to cause me to lose any sleep.
And I get my predicted error: methods defined in test_helper.rb that aren't in spec_helper.rb.
Adding to Spec helpers
I start moving methods over. One bad thing we did in test_helper.rb was to define the helper methods at the top level. So I'm putting them into modules, and then using config.extend and config.include for class and instance methods, respectively. (I believe I learned about this from the RSpec book. There should be documentation online for this as well.)
One thing I notice is that I'm not requiring webrat in spec_helper.rb, and I did require it explicitly in my CMS app. Requiring in the YAGS app doesn't revert me to the nokogiri/nokogiri error from before, so I'll leave the require in.
However, I get one method moved over, and suddenly I'm getting a complaint about shoulda's should_have_many.
Shoulda Gem with RSpec and Test::Unit
undefined method `should_have_many' for UserTest:Class
After fighting with this for a while, here's what I've figured out. The excellent shoulda gem is being loaded; the context method works just fine. So it's not a load path problem or other directory issue. Poking around in the code, shoulda.rb itself asks this question: defined? Spec and loads different libraries depending on what you pick. That is, you can have shoulda code for RSpec or Test::Unit, but not both!
Keep in mind that while I'm converting to RSpec, the tests themselves are still very much in a Test::Unit form. RSpec is supposed to handle them without too many problems.
Perhaps there are some games I could play with requiring files myself, but I'm not sure how compatible the RSpec and Test::Unit versions are, and I figure: why not start the true coversion to RSpec now? So I end up moving the should_have_many assertion to a new describe block:
describe User do
it { should have_many :vials }
end
I also drop the underscore between should and have_many. (I'm fudging a bit on the chronology here because iI discovered this problem later.) I'm actually not sure if the describe block is completely necessary after this change, but since I want to move in that direction anyway, I'm going to keep going in that direction.
I keep this in the same file as the Test::Unit test case. Works fine, and so I go on to fix all of these shoulda problems.
should Is a Reserved Word
Well, not really, of course. Actually, the problem is that both shoulda and RSpec have it as a "reserved word", but the meaning is different. (I suppose this might be a definition of a Object- or Kernel-level method: a keyword whose meaning can be changed. It should (pardon the pun) be treated as a keyword, but it's meaning can be changed due to which class it's added to.)
All the old shoulda tests that use should "X" have to be turned into it "should X". Time for Replace in Path again!
There are only a few to change, and now finally the specs all run and 186 out of 288 fail! It appears I have more methods to move to spec_helper.
Smelly Code
Along the way, I discover that some of the helper methods are quite stinky. Or some of the tests that they "inspire" are quite stinky. The biggest stink comes from black-box testing the associations and their dependencies. That is, for example, we had tests that would use the fixture data to make sure the right vial had the right flies in it. And if we deleted that vial, the right flies would also go. shoulda and RSpec make this much easier:
it { should have_many(:flies).dependent(:destroy) }
It reads short and sweet, and it doesn't involve any data checking.
Finishing Is Only the Beginning
How poetic. Gag!
287 examples, 0 failures, 287 passed
Finished in 12.497872 seconds
If you look at where I started with my tests, there were 288 unit and functional tests. I've added some new ones, and deleted old ones, so 287 examples sounds pretty good to me.
I'm not finished with the conversion, of course. I've discovered quite a few unit tests for models that still have only their default "test true" stub! Also I plan to separate the controller and view examples; this I typically do when needed though.
I need one set of view examples soon: I "ignored" a assert_standard_layout helper method. This method did a lot of looking at the HTML generated by the standard layout. It gets called several times throughout the tests. This was always overkill, but it seemed like a good idea to me to make sure that each action was using the standard layout and wasn't screwing it up. RSpec allows one to separate out the rendering from the controlling, so for the RSpec examples, for the time being, I just wrote a version of the method that does nothing. I need to write targeted view examples that will make the same assertions about the standard layout and then not worry about the standard layout anywhere else in the examples.
Biggest Mistake
Realistically, this isn't a huge mistake, but it was sloppy: I forgot to create a git branch for all of this work; I've been doing it all on master! Consequently, I've been afraid to make commits along the way since I try to keep the master green on every commit (or merge).
Worse yet, I realized this mistake somewhat early on, and did nothing about it. I believe that there are ways to stash what you're working on, create a new branch, and use the stash on the new branch. (I believe it's even git-stash that let's you do this.) Even if that didn't work, would it have been so terrible to revert all of my changes, create a new branch, and start over from scratch? Even if I did that now, would it be that much work?