Upgrading With Impunity
(Thu Oct 09, 2003) [/Ruby] #
I just upgraded from Ruby 1.6.7 (ships with Mac OS X) to Ruby 1.8.0. Upgrading software in general can be a risky game, but in this case I was actually looking forward to it. Indeed, I wanted something to break.
When I first started looking into Ruby, I decided that I wanted to learn it by writing tests. That is, I wanted to explore the language and its libraries by poking and prodding it with chunks of example client code. But instead of using the equivalent of a main method containing example code, I wanted the examples to check their own results. In other words, I'd use tests to ask Ruby a question, learn from its response, then codify the answer in a test assertion. Then when I wanted to refresh my memory of a particular usage of Ruby, I'd refer back to the test. So over time I've grown a fairly comprehensive suite of learning tests -- things I know to be true about Ruby.
Flash-forward to today just after I'd upgraded to Ruby 1.8.0. What's the next step? Pull the test trigger:
Interesting. Two tests failed. 105 tests passed. What gives?[enoch:~/projects/ruby/learn] > ruby AllTests.rb Loaded suite AllTests Started ..............................................................................FF........................... Finished in 0.667704 seconds. 1) Failure!!! testObjectSpace(ReflectionTest) [./ReflectionTest.rb:12]: <45> expected but was <34> 2) Failure!!! testRangeIntrospection(ReflectionTest) [./ReflectionTest.rb:21]: <["extend", "equal?"]> expected but was <["to_a", "instance_eval"]> 107 tests, 243 assertions, 2 failures, 0 errors
Here's the first test that fails, with the failed assertion highlighted:
def testObjectSpace
a = 102.7
b = 95.1
living = Array.new
ObjectSpace.each_object(Numeric) { |x| living.push(x) }
assert_equal(45, living.length)
assert(living.include?(a))
assert(living.include?(b))
end
I'm learning introspection (reflection) here. The omniscient
ObjectSpace class knows about all living objects. I'm asking it to
traverse its universe of objects of type Numeric and then
I rack 'em up on a stack. Then, for a reason I can't quite recall now, I assert
that 45 Numeric objects are alive. Finally I assert that two
numbers I created are among the land of the living.
So why is the first assertion failing? Well, apparently in Ruby 1.8.0 there are
only 34 Numeric objects alive at the point this test is run. It's
an implementation detail really. In hindsight, I probably could have just
tested that my two numbers are alive. Testing for 45 objects is a
triviality.
Here's the second test that fails, again with the failed assertion highlighted:
def testRangeIntrospection
range = 1..10
methods = range.methods
assert_equal(68, methods.length)
assert_equal(["extend", "equal?"], methods[1..2])
assert(range.respond_to?("equal?"))
assert(!range.respond_to?("hasKey"))
end
I'm still learning introspection here. This time I create a
Range object, assert that it has 68 methods at
my disposal, test the name of the methods at the index 1 and 2, and then
demonstrate how to use respond_to? to see if the object will
respond to selected method invocations. It worked in Ruby 1.6.7. Seems that in
Ruby 1.8.0 the returned method names have been reordered. Does it matter?
Probably not. Again, the first two assertions are trivialities that test
implementation details. I can't remember why I wrote those assertions now. The
last two assertions are probably sufficient, but I was in a learning mode
at the time.
So should I just clean up these tests to be version independent? Doing so would
mean removing the trivialities -- making the tests more generic. More to the
point, do I care that version 1.8.0 has a few less Numeric
instances and might be using a different data structure to hold method names?
Well, I probably shouldn't care because I don't want to become dependent on
these sort of implementation details. But I think the tests are trying to tell
me something. I just don't get it (yet). And if I remove the assertions I
might miss it later.
So for now I'm just going to update the assertions -- trivialities and all -- to pass for Ruby 1.8.0, then see what happens the next time I upgrade. After all, running the tests is free. In the meantime, 105 passing tests tells me that my expectations about Ruby still hold true despite the upgrade. That is, while there may be new things in version 1.8.0 that I've yet to learn, upgrading hasn't invalidated what I already know. These learning tests have payed back once again, this time by giving me a lot of confidence going forward.
