DayRange (#92)

This solution does more or less the absolute minimum required by the
quiz, but it is fairly short and easy to follow, I believe. This is
my first time submitting a solution, and I’m fairly new to ruby.

Ideally, I’d like to make the class methods private, but I was having
some trouble getting that to work out. If I make them private as
things stand now, I get a no method error, “private method called” in
the constructor. I thought one could fix that by calling the class
methods without a reciever, but for some reason, when I call these
class methods without a reciever, I also get a no method error. To be
concrete, if I change the line

@string = DateRange.build_string(arr)

to

@string = build_string(arr),

when I run the unit tests below I get

NoMethodError: undefined method ‘build_string’ for :DateRange
./daterange.rb:21:in ‘initialize’
daterangetest.rb:7:in ‘test_init’

Any advice anyone could offer about how to do this
private_class_method buisiness correctly would be greatly appreciated.

– Jason M.

require ‘test/unit’

class DateRange

DAY_NUMBERS = {
				1 => 'Mon',
				2 => 'Tue',
				3 => 'Wed',
				4 => 'Thu',
				5 => 'Fri',
				6 => 'Sat',
				7 => 'Sun'
			}

# Creates a new DateRange from a list of representations of days of the 

week
def initialize(*args)

	# Convert the arguments to an array of day numbers
	arr = args.collect {|rep| DateRange.day_num(rep)}
	arr.uniq!
	arr.sort!

	@string = DateRange.build_string(arr)

end

# Given a sorted array of day numbers, build the string representation
def self.build_string(arr)
	result = ''

	while i = arr.shift

		# Add the day name to the string
		result << "#{DAY_NUMBERS[i]}"

		# If there is a run of 3 or more consecutive days, add a '-' 

character,
# and then put the last day of the run after it
if arr[1] == i + 2
result << ‘-’
i = arr.shift while arr.first == i + 1
result << “#{DAY_NUMBERS[i]}”
end

		# Unless this is the last day
		result << ', ' if arr.first
	end

	result
end

# Returns the number representation of a day of the week specified by 

number,
# name, or abbreviation
#
# DateRange.day_num(2) => 2
# DateRange.day_num(‘Fri’) => 5
# DateRange.day_num(‘saturday’) => 6
def self.day_num(rep)
if (1…7).include?(rep.to_i)
rep.to_i
else
result = DAY_NUMBERS.index(rep[0,3].capitalize)
raise ArgumentError unless result
result
end
end

def to_s
	@string
end

end

class DateRangeTest < Test::Unit::TestCase

def test_init
	assert_equal('Mon-Sun', DateRange.new(1,2,3,4,5,6,7).to_s)
	assert_equal('Mon-Wed, Sat, Sun', DateRange.new(1,2,3,6,7).to_s)
	assert_equal('Mon, Wed-Sat', DateRange.new(1,3,4,5,6).to_s)
	assert_equal('Tue-Thu, Sat, Sun', DateRange.new(2,3,4,6,7).to_s)
	assert_equal('Mon, Wed, Thu, Sat, Sun', DateRange.new(1,3,4,6,7).to_s)
	assert_equal('Sun', DateRange.new(7).to_s)
	assert_equal('Mon, Sun', DateRange.new(1,7).to_s)
	assert_equal('Mon-Fri', DateRange.new(*%w(Wednesday Monday Tuesday

Thursday Friday Monday)).to_s)
assert_raise(ArgumentError) {DateRange.new(1,8)}
end

end

tOn 8/27/06, Boris P. [email protected] wrote:

my solution is definitely over-engineered :slight_smile:

On the opposite end, I’ve been thinking about codegolf too much.
Here’s an update to my under-engineered solution, which meets all the
requirements in 3 statements. It could be even shorter, but I wanted
to keep some possibility of someone else reading it.

class DayRange
Dnames = [nil]+(1…7).map{|n|Time.gm(1,1,n).strftime(“%a”)}
def initialize *l
@range=l.map{|v|v.respond_to?(:split) ? v.split(‘,’) :
v}.flatten.map{|v|(n=v.to_i)>0 ? n :
(1…7).find{|i|/#{Dnames[i]}/i=~v.to_s}||raise(“ArgumentError:
#{v}”)}.sort
end
def to_s
@range.map{|e|“#{”-" if e-1==@l}#{Dnames[@l=e]}"}.join(‘,
‘).gsub(/(, -\w+)+, -/,’-’).gsub(/ -/,’ ')
end
end

p DayRange.new( 1,3,4,5,6).to_s
p DayRange.new(“Tuesday,Wednesday,Sunday”).to_s
p DayRange.new(1,“tuesday,wed,5”,“6,7”).to_s

On Aug 27, 2006, at 3:25 PM, Morus W. wrote:

one remark:
I don’t like the suggested interface (though I used and didn’t work
out an
alternative). It does not seem reasonable to instanciate an object
providing
all the information in the constructor and the only thing one can
do with
that object is call one method once (multiple calls to to_s should
be avoided
for performance).

Well, if you allow the custom day names and control that with an
argument passed to to_s() calling it multiple times seems
reasonable. Here’s my own offering allowing that:

class DayRange
SHORT_NAMES = %w[Mon Tue Wed Thu Fri Sat Sun].freeze
LONG_NAMES = %w[ Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday ].freeze

def initialize(*days)
@days = days.map do |d|
ds = d.to_s.downcase.capitalize
SHORT_NAMES.index(ds) || LONG_NAMES.index(ds) || d - 1
end rescue raise(ArgumentError, “Unrecognized number format.”)
unless @days.all? { |d| d.between?(0, 6) }
raise ArgumentError, “Days must be between 1 and 7.”
end
raise ArgumentError, “Duplicate days given.” unless @days ==
@days.uniq
end

def to_s(names = SHORT_NAMES)
raise ArgumentError, “Please pass seven day names.” unless
names.size == 7

 @days.inject(Array.new) do |groups, day|
   if groups.empty? or groups.last.last.succ != day
     groups << [day]
   else
     groups.last << day
     groups
   end
 end.map { |g| g.size > 2 ? "#{g.first}-#{g.last}" : g.join(", ") }.
     join(", ").gsub(/\d/) { |i| names[i.to_i] }

end
end

END

James Edward G. II

Does anybody have some idea why my posts are getting garbage chars
(‘=20’
and ‘=3D’) added on the archive site:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/211029

It appears to affect only me, and other archives of ruby-talk such as
this
one:

do not have the same issue. I would really like to fix this, but I have
no
clue what may be causing it. Any suggestions are welcome…

Thanks,
-d

My second solution builds off of seeing one line of Morus’ solution:

Morus W. [email protected] wrote:

self.inject( [ [] ] ) do | list, item |
(which helped, because I rarely remember inject)

and a new solution was born:

class DayRange
NAMEMAP={“Mon”=>1, “Tue”=>2, “Wed”=>3, “Thu”=>4, “Fri”=>5, “Sat”=>6,
“Sun”=>7, “Thurs”=>4, “Monday”=>1, “Tuesday”=>2, “Wednesday”=>3,
“Thursday”=>4, “Friday”=>5, “Saturday”=>6, “Sunday”=>7}

REVERSENAMEMAP=[nil, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”, “Sun”]

def initialize(*args)
#parse arguments into Integers from 1 to 7
args.collect!{|x| NAMEMAP[x] || x}
args.sort!.uniq!
raise ArgumentError if args.any? do |x|
not x.is_a?(Fixnum) or not (1…7).include? x
end

  #turn everything into ranges
  @ranges=args.inject([]) do |a,v|
if a[-1]==nil or a[-1].last != v-1
  a << (v..v)
else
      #extend the existing range to include the new element
  a[-1]=((a[-1].first)..v)
end
a
  end

  #this code can be included if you would like wrap-around ranges
  #note that it constructs an ranges (with last<first) which doesn't
  #actually work with some ruby features. Hence, I don't use those
  #features which it breaks.

  #if @ranges[-1].last==7 and @ranges[0].first==1
  #   v=((@ranges[-1].first)..(@ranges[0].last))
  #   @ranges.delete_at(-1)
  #   @ranges.delete_at(0)
  #   @ranges << v
  #end

end

def to_s
#determine how to print each range based on the length of the
range
@ranges.collect do |r|
if r.first==r.last
REVERSENAMEMAP[r]
elsif r.first==r.last-1
“#{REVERSENAMEMAP[r.first]}, #{REVERSENAMEMAP[r.last]}”
else
“#{REVERSENAMEMAP[r.first]}-#{REVERSENAMEMAP[r.last]}”
end
end.join(", ")
end
end

puts DayRange.new(1,2,3,4,5,6,7).to_s
puts DayRange.new(1,2,3,6,7).to_s
puts DayRange.new(1,3,4,5,6).to_s
puts DayRange.new(2,3,4,6,7).to_s
puts DayRange.new(1,3,4,6,7).to_s
puts DayRange.new(7).to_s
puts DayRange.new(1,7).to_s
puts DayRange.new(1,8).to_s rescue puts “ArgumentError”

On 8/28/06, darren kirby [email protected] wrote:

Does anybody have some idea why my posts are getting garbage chars (‘=20’
and ‘=3D’) added on the archive site:

Someone smarter than me will have to fill in the blanks, but I’m
pretty sure it has to do with sending e-mails to the list as HTML
instead of plain text e-mails.

Hi,

Here is my solution. I’m a RubyNuby but I thought this one was easy so
I gave it a shot.

require ‘test/unit’

class DaysToString
Table= {‘mon’ => 1,‘tue’ => 2,‘wed’ => 3,‘thu’ => 4,
‘fri’ => 5,‘sat’ => 6,‘sun’ => 7,
‘monday’=>1,‘tuesday’=>2,‘wednesday’=>3,‘thursday’=>4,
‘friday’=>5,‘saturaday’=>6,‘sunday’=> 7,
1 => 1, 2 => 2, 3 => 3,4 => 4, 5 => 5,6 => 6, 7 => 7}
Days=[‘Mon’,‘Tue’,‘Wed’,‘Thu’,‘Fri’,‘Sat’,‘Sun’,]
def initialize *arg
arg.map! do |a|
a.downcase! if a.respond_to?(:downcase!)
raise(ArgumentError,“Wrong Format.”) if !Table.include?(a)
a = Table[a]
end
arg.sort!.uniq!
@data = arg
end

def to_s
temp =[]
txt = “”
loop do
[email protected]
if ((x.is_a?(NilClass) && !temp.empty?) || (!temp.empty? &&
temp.last!=x-1))
txt+= “#{Days[temp.first-1]}”
txt+= “,#{Days[temp[1]-1]}” if temp.length==2
txt+= “-#{Days[temp.last-1]}” if temp.length>2
txt+= ‘,’
temp.clear
break if x.is_a?(NilClass)
end
temp.push(x)
end
txt
end
end

class TestDaysToString < Test::Unit::TestCase
def test_all
assert_raise(ArgumentError)
{DaysToString.new(1,2,‘tester’,4,5,7).to_s}
assert_raise(ArgumentError) {DaysToString.new(8).to_s}
assert_raise(ArgumentError) {DaysToString.new(‘wacky_day’).to_s}
assert_equal(“Mon-Wed,Fri-Sun,”,DaysToString.new(1,2,3,5,6,7).to_s)
assert_equal(“Mon-Sun,”,DaysToString.new(1,2,3,4,4,5,4,6,7,1).to_s)
assert_equal(“Tue-Thu,”,DaysToString.new(2,3,4).to_s)
assert_equal(“Fri,”,DaysToString.new(5).to_s)
assert_equal(“Mon,Sat,Sun,”,DaysToString.new(‘sun’,‘mOnDaY’,6).to_s)
assert_equal(“Tue,Thu-Sun,”,DaysToString.new(4,5,6,7,2).to_s)
assert_equal(“Tue,Thu-Sun,”,DaysToString.new(4,5,6,7,2).to_s)
end
end

quoth the Lyle J.:

On 8/28/06, darren kirby [email protected] wrote:

Does anybody have some idea why my posts are getting garbage chars (‘=20’
and ‘=3D’) added on the archive site:

Someone smarter than me will have to fill in the blanks, but I’m
pretty sure it has to do with sending e-mails to the list as HTML
instead of plain text e-mails.

But I never send HTML mails…ever…

-d

quoth the darren kirby:

-d

Sorry to reply to self.

I had a look at my own sent messages using ‘view source’ in Kmail. The
extra
chars, whatever they are there too. It got me thinking…I turned off
signing
my messages, but it seems to have done nothing. Still there. Nothing in
my
Kmail settings seems relevant. Could it be the character encoding?

My messages aren’t appearing as HTML to you all are they? My settings
say
not…

Sorry again for OT chatter…

Thanks,
-d

darren kirby wrote:

My messages aren’t appearing as HTML to you all are they? My settings say
not…

Nope. But they are encoded in iso-8859-6, which I haven’t covered in
Thunderbird font settings and the default Courier New ticks me off. What
language set is that anyway? (I should send a hint to Mozilla people
that font substitution is a Good Thing, and that some people just want
to use one font for everything without clicking through all 20 language
groups, thankyouverymuch, since they can’t read one bit Chinese or
Indian anyway.)

David V.

On 8/28/06, darren kirby [email protected] wrote:

do not have the same issue. I would really like to fix this, but I have no
clue what may be causing it. Any suggestions are welcome…

Apparently, Kmail sends messages encoded (inappropriately, it seems)
as quoted-printable. This means that certain characters (unprintable
ones, and ones that have the high bit set to 1) are encoded by an
equals sign (=) followed by two hexadecimal digits. I’m not a Kmail
user, so I can’t give you a sure way to fix it, but a quick google
search tells me that it may help to tell Kmail to allow 8-bit encoding
for your messages. This may cause problems of its own as some mail
software may have problems with 8-bit characters, but that should be
OK as long as you stick to ASCII which is 7-bit. It may also help if
you could set your char-set to US-ASCII instead of iso-8859-6.

HTH

Peter

After looking through some of the other solutions to this quiz, I
decided to adapt some of the good ideas I found to improve my own
solution. Using Test::Unit to for testing was the first modification
I wanted to make. I have never used Test::Unit up to now. I thought
it was high time for me to do so.

Since changing over to Test::Unit doesn’t really change my solution,
I didn’t plan to repost it after making the change. I changed my mind
because I ran into something with Test::Unit that really surprised
me. First, the modified code and then some more discussion.

require 'test/unit'

class DayRange

DAY_DIGITS = {
   'mon' => 1,
   'tue' => 2,
   'wed' => 3,
   'thu' => 4,
   'fri' => 5,
   'sat' => 6,
   'sun' => 7,
   'monday' => 1,
   'tuesday' => 2,
   'wednesday' => 3,
   'thursday' => 4,
   'friday' => 5,
   'saturday' => 6,
   'sunday' => 7,
   '1' => 1,
   '2' => 2,
   '3' => 3,
   '4' => 4,
   '5' => 5,
   '6' => 6,
   '7' => 7
}

SHORT_NAMES = %w[_ Mon Tue Wed Thu Fri Sat Sun].freeze

LONG_NAMES = %w[_ Monday Tuesday Wednesday Thursday
                Friday Saturday Sunday].freeze

# Return day range as nicely formatted string.
# If @long is true, day names appear in long form; otherwise, they
# appear in short form.
def to_s
   names = @long ? LONG_NAMES : SHORT_NAMES
   result = []
   @days.each do |d|
      case d
      when Integer
         result << names[d]
      when Range
         result << names[d.first] + "-" + names[d.last]
      end
   end
   result.join(", ")
end

# Return day range as array of integers.
def to_a
   result = @days.collect do |d|
      case d
      when Integer then d
      when Range then d.to_a
      end
   end
   result.flatten
end

def initialize(*args)
   @days = []
   @long = false
   @args = args
   @args.each do |arg|
      case arg
      when Integer
         bad_arg if arg < 1 || arg > 7
         @days << arg
      when /^(.+)-(.+)$/
         begin
            d1 = DAY_DIGITS[$1.downcase]
            d2 = DAY_DIGITS[$2.downcase]
            bad_arg unless d1 && d2 && d1 <= d2
            d1.upto(d2) {|d| @days << d}
         rescue StandardError
            bad_arg
         end
      else
         d = DAY_DIGITS[arg.downcase]
         bad_arg unless d
         @days << d
      end
   end
   @days.uniq!
   @days.sort!
   normalize
end

Use this to change printing behavior from short day names to long day

names or vice-versa.

attr_accessor :long

private

# Convert @days from an array of digits to normal form where runs of
# three or more consecutive digits appear as ranges.
def normalize
   runs = []
   first = 0
   for k in [email protected]
      unless @days[k] == @days[k - 1].succ
         runs << [first, k - 1] if k - first > 2
         first = k
      end
   end
   runs << [first, k] if k - first > 1
   runs.reverse_each do |r|
      @days[r[0]..r[1]] = @days[r[0]]..@days[r[1]]
   end
end

def bad_arg
   raise(ArgumentError,
         "Can't create a DayRange from #{@args.inspect}")
end

end

class TestDayRange < Test::Unit::TestCase

# All these produce @days == [1..7].
ONE_RANGE = [
   %w[mon tue wed thu fri sat sun],
   %w[monDay tuesday Wednesday Thursday friDAY saturday SUNDAY],
   %w[mon-fri sat-sun],
   %w[4-7 1-3],
   (1..7).to_a.reverse,
   [4, 7, 6, 5, 4, 1, 2, 1, 2, 3, 3, 7, 6, 5],
]

# Both these produce @days == [1..3, 5..7].
TWO_RANGES = [
   %w[mon-mon tue-tue wed-wed fri-sun],
   [1, 2, 'mon-wed', 'friday', 6, 7]
]

INVALID_ARGS = [
   [1, 2, 'foo'],
   %w[foo-bar],
   %w[sat-mon],
   (0..7).to_a.reverse,
   (1..8).to_a
]

@@one_range = []
@@two_ranges = []

def test_args_helper(args, expected)
   obj = nil
   assert_nothing_raised(ArgumentError) {obj = DayRange.new(*args)}
   assert_equal(expected, obj.instance_variable_get(:@days))
   obj
end

def test_valid_args
   ONE_RANGE.each do |args|
      @@one_range << test_args_helper(args, [1..7])
   end
   TWO_RANGES.each do |args|
      @@two_ranges << test_args_helper(args, [1..3, 5..7])
   end
   puts "test_valid_args -- #{@@one_range.size}, #

{@@two_ranges.size}"
end

def test_bad_args
   puts "test_bad_args"
   INVALID_ARGS.each do |args|
      assert_raise(ArgumentError) {DayRange.new(*args)}
   end
end

def test_to_s
   puts "test_to_s -- #{@@one_range.size}, #{@@two_ranges.size}"
   @@one_range.each do |obj|
      assert_equal('Mon-Sun', obj.to_s)
   end
   @@two_ranges.each do |obj|
      assert_equal('Mon-Wed, Fri-Sun', obj.to_s)
   end
end

def test_to_a
   puts "test_to_a -- #{@@one_range.size}, #{@@two_ranges.size}"
   @@one_range.each do |obj|
      assert_equal((1..7).to_a, obj.to_a)
   end
   @@two_ranges.each do |obj|
      assert_equal([1, 2, 3, 5, 6, 7], obj.to_a)
   end
end

end

Loaded suite /Users/mg/Projects/Ruby/Ruby Q./Quiz 92/quiz_92 Started test_bad_args .test_to_a -- 0, 0 .test_to_s -- 0, 0 .test_valid_args -- 6, 2 . Finished in 0.010845 seconds.

4 tests, 21 assertions, 0 failures, 0 errors

None of the assertions in test_to_s and test_to_a execute. This is
because @@one_range and @@two_ranges are both empty when these tests
are run. My plan of creating the test objects in test_valid_args and
reusing them in test_to_s and test_to_a fails. This happens because
the tests are run, not in the order in which they appear in the
source code, but in the sort order of their names. How strange!

The fix is obvious and I have already applied it: rename
test_valid_args to test_args so it runs first.

I’ve posted this because it took me longer to trace down this problem
than it took me to write the TestDayRange class. I will consider it
worth the effort if the posting helps one other person to avoid this
trap. On the other hand, if I’m guilty of belaboring a well-known
“feature” of Test::Unit, I apologize.

Regards, Morton

quoth the David V.:

Indian anyway.)
Hmm. In my Kmail settings I have this:

“This list is checked for every outgoing message from the top to the
bottom
for a charset that contains all required characters”
us-ascii
iso-8859-1
utf-8

So where the heck is iso-8859-6 coming from. I have explicitly sent
this as
us-ascii, and if it seems to work I will send my ruby-quiz solutions the
same
way.

David V.

Thanks for the hints guys,
-d

The fix is obvious and I have already applied it: rename
test_valid_args to test_args so it runs first.

Test::Unit provides the functionality you need in the “setup” and
“teardown”
methods, which are run before and after each test_* method in your
class.

tim

I can’t see how setup and teardown would help in this case. They are
run as before and after methods for each test, not as initializer and
finalizer for the whole test case. Also, recall that I don’t want to
create any test objects outside of test_valid_args. I only want to
use objects that have already been validated by this test in the
other tests. To me, that means test_valid_args MUST run first.

However, being new to Test::Unit, I may well be missing something.
Could you post an example showing how setup and teardown could be
used to avoid the trap I fell into?

Regards, Morton

On Aug 30, 2006, at 11:55 AM, Morton G. wrote:

Could you post an example showing how setup and teardown could be
used to avoid the trap I fell into?

Here’s my modification to your tests to get them working with setup():

class TestDayRange < Test::Unit::TestCase

# All these produce @days == [1..7].
ONE_RANGE = [
   %w[mon tue wed thu fri sat sun],
   %w[monDay tuesday Wednesday Thursday friDAY saturday SUNDAY],
   %w[mon-fri sat-sun],
   %w[4-7 1-3],
   (1..7).to_a.reverse,
   [4, 7, 6, 5, 4, 1, 2, 1, 2, 3, 3, 7, 6, 5],
]

# Both these produce @days == [1..3, 5..7].
TWO_RANGES = [
   %w[mon-mon tue-tue wed-wed fri-sun],
   [1, 2, 'mon-wed', 'friday', 6, 7]
]

INVALID_ARGS = [
   [1, 2, 'foo'],
   %w[foo-bar],
   %w[sat-mon],
   (0..7).to_a.reverse,
   (1..8).to_a
]

def setup
  assert_nothing_raised(ArgumentError) do
    @one_range = ONE_RANGE.map { |args| DayRange.new(*args) }
  end
  assert_nothing_raised(ArgumentError) do
    @two_ranges = TWO_RANGES.map { |args| DayRange.new(*args) }
  end
end

def test_valid_args
   @one_range.each do |obj|
      assert_expected_days([1..7], obj)
   end
   @two_ranges.each do |obj|
      assert_expected_days([1..3, 5..7], obj)
   end
end

def test_bad_args
   INVALID_ARGS.each do |args|
      assert_raise(ArgumentError) { DayRange.new(*args) }
   end
end

def test_to_s
   @one_range.each do |obj|
      assert_equal('Mon-Sun', obj.to_s)
   end
   @two_ranges.each do |obj|
      assert_equal('Mon-Wed, Fri-Sun', obj.to_s)
   end
end

def test_to_a
   @one_range.each do |obj|
      assert_equal((1..7).to_a, obj.to_a)
   end
   @two_ranges.each do |obj|
      assert_equal([1, 2, 3, 5, 6, 7], obj.to_a)
   end
end

private

def assert_expected_days(expected, day_range)
  assert_equal(expected, day_range.instance_variable_get(:@days))
end

end

James Edward G. II

On Aug 30, 2006, at 2:40 PM, Morton G. wrote:

Thanks for trying to help, but if I understand how setup functions,
your modification defeats my requirement that the test objects be
created once and once only. As I understand it, setup will run
before EACH test and the test objects will be created over and over
again, even for test_bad_args where they are not needed at all.

My opinion is that this is a very good thing. Tests should work in
isolation as much as possible.

You are testing one small part of the whole, to verify correctness.
When you start sharing details between the tests you tie them right
back into a complete system again and that’s what unit testing is
trying to avoid. Requiring that tests be run in a given order is
just too fragile.

When I do require that some tests work sequentially, I do it like this:

def test_me_first
# …
end

def test_me_second
test_me_first

 # ...

end

I only feel safe counting on the order when I get to say what the
order is, using the above.

This does extra work, as you complained about with my implementation
of setup(). Ruby doesn’t mind the exercise though and as long as she
can do it quickly, I don’t either. Besides, it shoots that test
counter right on up! (Makes me feel great, “These tests are just
flying by…”)

Renaming test_valid_args to test_args is not only simpler, it meets
the requirement of parsimony of test objects.

I’m very convinced this is the wrong way to go. You are relying on
an implementation detail of Test::Unit here, that could change at
anytime. That could cause your tests to start magically failing at
some point down the road.

I also don’t believe you have the order correct. Your tests run as
expected on my box with no modification to the method names. I
haven’t gone into the source of Test::Unit to determine why this is,
but it could be that the methods are hashed in which case you can’t
count on any order at all.

James Edward G. II

On Aug 30, 2006, at 4:00 PM, James Edward G. II wrote:

isolation as much as possible.

You are testing one small part of the whole, to verify
correctness. When you start sharing details between the tests you
tie them right back into a complete system again and that’s what
unit testing is trying to avoid. Requiring that tests be run in a
given order is just too fragile.

I appreciate the point you are making here. My concept, and I admit
it is the concept of a complete Test::Unit newbie, was that the test
case class was the unit of test, not the individual tests defined in
the class. I guess I need to read up on the philosophy behind unit
testing. Know any good references?

Also, I still don’t like the idea of creating test objects over and
over again, especially for tests that don’t use them at all. Maybe my
old (and now bad) habits formed over years of working on machines
with limited resources are leading me astray, but – at least for now
– I can’t overcome my distaste for such extravagance.

# ...

end

I only feel safe counting on the order when I get to say what the
order is, using the above.

This is good advice. I’ll try to heed it.

an implementation detail of Test::Unit here, that could change at
anytime. That could cause your tests to start magically failing at
some point down the road.

You’ve almost convinced me, too :wink:

I also don’t believe you have the order correct. Your tests run as
expected on my box with no modification to the method names. I
haven’t gone into the source of Test::Unit to determine why this
is, but it could be that the methods are hashed in which case you
can’t count on any order at all.

I’m not sure what you mean by not correct. My results printout speaks
for itself, doesn’t it?
I agree that its being different on your box is troubling. It makes,
as you point out, relying on any particular order indefensible.

One thing is for sure – your comments are sending me back to redo my
test case class. I’m not sure where I will take it (I’m still
resisting you approach using setup), but I will change it so it
doesn’t rely on the order in which the tests are run. Oh well, I
wanted to learn about Test::Unit and I’m certainly doing that :slight_smile:

Regards, Morton

On Aug 30, 2006, at 3:48 PM, Morton G. wrote:

I guess I need to read up on the philosophy behind unit testing.
Know any good references?

You know, I’ve read a couple of books on the subject now and still
have not found one I just fell in love with. Hopefully others will
chime in with some good suggestions.

Also, I still don’t like the idea of creating test objects over and
over again, especially for tests that don’t use them at all. Maybe
my old (and now bad) habits formed over years of working on
machines with limited resources are leading me astray, but – at
least for now – I can’t overcome my distaste for such extravagance.

Remember, these are unit tests, not nuclear missile guidance
systems. The code is only run when you ask it to run as a tool to
help you develop your code.

The goal is to make that testing process as painless as possible, not
to win awards for speed and low memory consumption.

This is also very common in unit testing. Rails for example reloads
database fixtures before every single test, whether the test uses
them or not. That’s a substantially higher commitment than your
eight DayRange objects. :wink: (To be fair though, the Rails team has
had to work hard to get that fixture load time down. Slow tests are
bad!)

I also don’t believe you have the order correct. Your tests run
as expected on my box with no modification to the method names. I
haven’t gone into the source of Test::Unit to determine why this
is, but it could be that the methods are hashed in which case you
can’t count on any order at all.

I’m not sure what you mean by not correct. My results printout
speaks for itself, doesn’t it?

Oops, I misunderstood them. My bad. The situation you described
is the same on my box. Sorry to spread confusion.

James Edward G. II

Thanks for trying to help, but if I understand how setup functions,
your modification defeats my requirement that the test objects be
created once and once only. As I understand it, setup will run before
EACH test and the test objects will be created over and over again,
even for test_bad_args where they are not needed at all.

Renaming test_valid_args to test_args is not only simpler, it meets
the requirement of parsimony of test objects.

Regards, Morton