Given a class
LeapYear
with method
isleap?
and a data file consisting of year, isleap(true/false) pairs, we want to generate individual tests for each line of data. Using Ruby, this is quite simple to do. One way is to read the file, and build a string of the code, then write that to a file and then load it. That would certainly work, but using
define_method
is a bit more interesting. Here is the code my partner Clay Smith and I came up with:
require 'test/unit'
require 'leapyear'
class LeapYearTest < Test::Unit::TestCase
def setup
@ly = LeapYear.new
end
def LeapYearTest.generate_tests
filename = "testdata.dat"
file = File.new(filename, "r") #reading the file
file.each_line do |line| #iterate over each line of the file
year, is_leap = line.split; #since a space separates the year from if it is a leap year or not, we split the line along a space
code = lambda { assert_equal(is_leap.downcase=="true", @ly.isleap?(year.to_i)) } #create some code
define_method("test_isleap_" + year, code) #define the method, and pass in the code
end
file.close
end
end
LeapYearTest.generate_tests
One thing to note, that I initially had trouble with, was the
to_i
. At first, it never occurred to me that I should be using it, since with Coldfusion a string which is a number can have operations performed on it as if it were a number. In Ruby, I needed the to_i, as isleap? was always returning false with the
String
version of year.
A more interesting item to note is that in the line where we define the method, if you were to attach a block like this:
define_method("test_isleap_"+year) { assert_equal(is_leap.downcase=="true", @ly.isleap?(year.to_i)) }
Then the solution will not work. It creates the correct methods, but when it evaluates them, it appears as though it will use the last value of year, rather than the value at the time of creation.
Hey! Why don't you make your life easier and subscribe to the full post
or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate
wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!
Leave a comment
Ahem, guess what, Dr. Venkat gives this same question again this year for his Ruby on Rails class.
The solution looks good. Did you get a 10 on this one? ^_^
Posted by
Dat Chu
on Mar 30, 2007 at 08:06 PM UTC - 6 hrs
Honestly, I forget what the grade was, and I'm hesitant to call anything a 10, because I'm not sure I got one the entire semester =)... But, I do remember having TONS of trouble with this, mainly due to the last code block there. That took forever to figure out what was going wrong!
Posted by
Sam
on Mar 31, 2007 at 09:14 AM UTC - 6 hrs
Thanks for the helpful post.
Can you post a little more detail as to why the last value of year gets used each time, when the define_method is put on a single line?
define_method("test_isleap_"+year) { assert_equal(is_leap.downcase=="true", @ly.isleap?(year.to_i)) }
How does a lambda closure stop this from happening?
Posted by Sam
on Nov 19, 2007 at 03:29 AM UTC - 6 hrs
Hi Sam,
I never took the time to figure out why the behavior was different. I did a little experimenting today, and cannot seem to find a difference either.
My best guess is that in the 2nd version, the binding to local variables happens later than the first (well, I suppose that much is obvious). What's not obvious is why.
I'm going to post that question on Ruby Talk and see if some of the gurus there can help me understand.
Posted by
Sammy Larbi
on Nov 19, 2007 at 03:05 PM UTC - 6 hrs
The best I can tell now is that either it was a problem that got fixed in a newer version of Ruby, or I was mistaken when I wrote it.
Posted by
Sammy Larbi
on Nov 20, 2007 at 09:46 AM UTC - 6 hrs
Leave a comment