Unit testing an each function

I am testing a new class I have written that has an each method, how do I write a test for this?

Build an array from the each and compare it to another array with the correct data in it or is there a cleaner way of doing this?

The plus point here is that the results of the each will be a plain integer sequence.

I've done it that way before. If you mix in Enumerable you can also use:

assert_equals(correct_values, test_class.to_a)

Hope that helps.

James Edward Gray II

···

On Oct 18, 2005, at 4:43 AM, Peter Hickman wrote:

I am testing a new class I have written that has an each method, how do I write a test for this?

Build an array from the each and compare it to another array with the correct data in it or is there a cleaner way of doing this?

That sounds fine to me. What's wrong with that method?

Actually, you don't need to build an array with each at all. IIRC,
Enumerable defines to_a (I'm assuming you're including that, right?)
based on each, so you can simply do something like

  fibSequence = FibbonacciSequence.new(9)
  reference = [1, 1, 2, 3, 5, 8 ,13, 21, 34]
  assert_equal reference, fibSequence.to_a

Kevin Ballard wrote:

That sounds fine to me. What's wrong with that method?

At the moment the source for the tests looks like poetry, very terse and clean, where everything is a bold statement that does something. Then building up arrays of expected results and collecting the output to be tested looks very clumsy in the middle of the elegant tests. It's more of an aesthetic thing, if it looks 'ugly' (and code in ruby tends to look beautiful) then I start to wonder if perhaps I could be doing this differently.

Pity you cant seem to do @data.each.each {|i| yield i} as it seems a perfect one-liner to me.

Peter Hickman wrote:

Pity you cant seem to do @data.each.each {|i| yield i} as it seems a
perfect one-liner to me.

I'm not quite sure what you would want that line to do. Can you give an
example of less terse, actually functional code that illustrates your
desire?

Kevin Ballard wrote:

Peter Hickman wrote:

Pity you cant seem to do @data.each.each {|i| yield i} as it seems a
perfect one-liner to me.
   
I'm not quite sure what you would want that line to do. Can you give an
example of less terse, actually functional code that illustrates your
desire?

Sort of like this (trivial example)

data = Array.new
data << (1..5)
data << (10..20)

data.each{|i| puts i}

1..5
10..20

# What I want to work is

data.each.each{|i| puts i}

1
2
3
4
5
10
11
12
13
14
15
16
17
18
19
20

# What I have to write is

data.each{|i| i.each{|j| puts j}}

No great hardship I know but I actually expected data.each.each{...} to
work.

If you know there's going to be Enumerable objects in there, perhaps you could redefine each() to handle the nesting, or provide another iterator for this...

James Edward Gray II

···

On Oct 18, 2005, at 10:04 AM, Peter Hickman wrote:

# What I want to work is

data.each.each{|i| puts i}

Peter Hickman wrote:

Sort of like this (trivial example)

data = Array.new
data << (1..5)
data << (10..20)

data.each{|i| puts i}

1..5
10..20

# What I want to work is

data.each.each{|i| puts i}

1
2
3
4
5
10
11
12
13
14
15
16
17
18
19
20

# What I have to write is

data.each{|i| i.each{|j| puts j}}

No great hardship I know but I actually expected data.each.each{...} to
work.

Huh, that's an interesting idea. Lets see if we can get it to work.

class EachProxy < Object
  instance_methods.each { |meth| undef_method(meth) unless meth.to_s =~
/^__/ }

  def initialize(obj)
    @obj = obj
  end

  def method_missing(meth, *args, &block)
    @obj.each { |item| item.send meth, *args, &block }
  end
end

class Array
  alias :__each__ :each
  def each(&blk)
    if blk
      __each__(&blk)
    else
      EachProxy.new(self)
    end
  end
end

Now your example works fine.

James Edward Gray II wrote:

···

On Oct 18, 2005, at 10:04 AM, Peter Hickman wrote:

# What I want to work is

data.each.each{|i| puts i}

If you know there's going to be Enumerable objects in there, perhaps you could redefine each() to handle the nesting, or provide another iterator for this...

James Edward Gray II

It's no great hardship it is just that I was taken aback when data.each.each{...} didn't work, it seemed like the right thing to type at that point.

Kevin Ballard wrote:

class EachProxy < Object
  instance_methods.each { |meth| undef_method(meth) unless meth.to_s =~
/^__/ }

  def initialize(obj)
    @obj = obj
  end

  def method_missing(meth, *args, &block)
    @obj.each { |item| item.send meth, *args, &block }
  end
end

class Array
  alias :__each__ :each
  def each(&blk)
    if blk
      __each__(&blk)
    else
      EachProxy.new(self)
    end
  end
end

BTW, this also makes things like the following possible:

  ary = %w{a list of words goes here}
  ary.each.gsub!(/[aeiou]/, '') # returns the list minus vowels

But of course it's only useful for side-effect functions (like gsub!)
or functions that take blocks (like each).

Perhaps I should extend collect to work similarly as well? Currently
collect with no arguments simply returns the array unchanged, but
returning a similar proxy object might be nicer. It would let you do
things like:

  ary = %w{a list of words goes here}
  ary.collect.length # returns the length of all words

I still recommend a change. If you are always meant to iterator two levels deep, fix each(). If it's a common option, add another iterator for it. Make it easy on user code, much of which might just be written by you. :wink:

My two cents.

James Edward Gray II

···

On Oct 18, 2005, at 10:25 AM, Peter Hickman wrote:

It's no great hardship it is just that I was taken aback when data.each.each{...} didn't work, it seemed like the right thing to type at that point.

class EnumerableProxy < Object
  instance_methods.each { |meth| undef_method(meth) unless meth.to_s =~
/^__/ }

  def initialize(obj, meth, *args)
    @obj = obj
    @meth = meth
    @args = args
  end

  def method_missing(meth, *args, &block)
    @obj.send(@meth, *@args) { |item| item.send meth, *args, &block }
  end
end

class Array
  alias :__each__ :each
  def each(&blk)
    if blk
      __each__(&blk)
    else
      EnumerableProxy.new(self, :each)
    end
  end

  remove_method :collect
end

module Enumerable
  alias :__collect__ :collect
  def collect(&blk)
    if blk
      __collect__(&blk)
    else
      EnumerableProxy.new(self, :collect)
    end
  end
end

There, now you can do something like:

  ary = %w{a long list of words}
  lengths = ary.collect.length

Of course, Array#collect! still behaves as it used to. However, this
new #collect behaviour is defined in Enumerable so it will work in more
than just Array (although the #each stuff is on Array alone).

Thanks for that, quite an education in that code.

Ok, here's a new version. It now generalizes the wrapping, so it's
trivial to add it to new enumerable classes:

class EnumerableProxy < Object
  instance_methods.each { |meth| undef_method(meth) unless meth.to_s =~
/^__/ }

  def initialize(obj, meth, *args)
    @obj = obj
    @meth = meth
    @args = args
  end

  def method_missing(meth, *args, &block)
    @obj.send(@meth, *@args) { |item| item.send meth, *args, &block }
  end
end

module Enumerable
  @@meth_cache = Hash.new
  def self.meth_cache
    @@meth_cache
  end

  # I'm using a string for module_eval instead of a block
  # because using a block necessitates using define_method,
  # and methods created with define_method can't handle a block
  # (because Proc's can't handle blocks)
  def self.wrap_meth(klass, meth)
    meth_sym = meth.to_sym.inspect
    klass.module_eval <<-END
      meth = instance_method(#{meth_sym})
      Enumerable.meth_cache[#{klass.name}] ||= Hash.new
      Enumerable.meth_cache[#{klass.name}][#{meth_sym}] = meth
      def #{meth.to_s}(&blk)
        if blk
          meth = Enumerable.meth_cache[#{klass.name}][#{meth_sym}]
          meth.bind(self).call(&blk)
        else
          EnumerableProxy.new(self, #{meth_sym})
        end
      end
    END
  end

  wrap_meth self, :collect
end

Enumerable.wrap_meth Array, :each
Enumerable.wrap_meth Array, :collect
Enumerable.wrap_meth Array, :collect!

Unfortunately, due to limitations in Proc (documented in the comment
above), I had to use the ugly string form of module_eval.

I also had to use a method cache on Enumerable itself instead of the
individual classes, becuase when I tried it on the individual classes
it wasn't working right (seemed to be accessing Enumerable anyway, so I
was getting cross-class method binding issues).

Be careful when using irb with this, as it the proxy can cause
confusion (since the proxy forwards #inspect along with everything
else). Normally calling #collect without a block is basically the same
as calling #to_a, so "foo".collect normally returns ["foo"]. However,
once you've loaded this code, "foo".collect returns an EnumerableProxy
object, which irb will display as just "foo" because it'll forward the
#inspect message irb sends on to "foo".

Usage of this is pretty easy. Wrap any methods you want with
Enumerable.wrap_method class, :method. Of course it only makes sense on
enumerable methods. Feel free to wrap any more of Enumerable's
built-ins, and any classes you want to use #each with you have to wrap
separately (along with any class-specific implementations of Enumerable
methods, like Array#collect). Interestingly, this seems to work
perfectly fine on destructive methods like Array#collect!.

At this point I'm considering wrapping this up in a gem for
distribution. Any thoughts?

It looks interesting, but I think you are replicating the work done by
Nobu Nakada with the Enumerator class. For example, read this:

http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/11669710e085ca07

But it seems this newer functionality won't be backported to 1.8, so
your code may be useful in the meantime. I'm also not sure if the
enumerator version works exactly the same as yours.

Also, while this is cool, I find it somewhat confusing since it
changes Ruby iterator semantics quite a bit.

Ryan

···

On 10/18/05, Kevin Ballard <kballard@gmail.com> wrote:

At this point I'm considering wrapping this up in a gem for
distribution. Any thoughts?

Ryan Leavengood wrote:

>
> At this point I'm considering wrapping this up in a gem for
> distribution. Any thoughts?

It looks interesting, but I think you are replicating the work done by
Nobu Nakada with the Enumerator class. For example, read this:

http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/11669710e085ca07

But it seems this newer functionality won't be backported to 1.8, so
your code may be useful in the meantime. I'm also not sure if the
enumerator version works exactly the same as yours.

Hrm, interesting, but that doesn't really shed much light on what
Enumerator is going to do. Is it going to be like a built-in Generator,
or what? Is there another thread that talks about it?

Also, while this is cool, I find it somewhat confusing since it
changes Ruby iterator semantics quite a bit.

Yeah, but it only affects the case where you don't pass a block, and
that's not very useful in the default functionality (for #each it
raises an error, for #collect it appears to just be equivalent to
#to_a, dunno about the rest but they're all expecting blocks).

The talk about Enumerator does have me concerned, though, because if
anybody used my code, and the Enumerator thing goes into 2.0, when they
upgrade from Ruby 1.8 to 2.0 it's gonna break.

···

On 10/18/05, Kevin Ballard <kballard@gmail.com> wrote:

Kevin Ballard wrote:

Hrm, interesting, but that doesn't really shed much light on what
Enumerator is going to do. Is it going to be like a built-in Generator,
or what? Is there another thread that talks about it?

Hrm, I just discovered that Enumerable::Enumerator already exists
(require 'enumerator'), but I can't find docs on it. ruby-doc.org gives
me a 404 on the enumerator page, a google search turns up pages in
Japanese, and I can't find a file it's loaded from (and SCRIPT_LINES__
isn't populated when I require 'enumerator') which leads me to believe
it's written in C and thus I can't RTSL for docs.

Any pointers?

Kevin Ballard wrote:

Kevin Ballard wrote:
> Hrm, interesting, but that doesn't really shed much light on what
> Enumerator is going to do. Is it going to be like a built-in Generator,
> or what? Is there another thread that talks about it?

Hrm, I just discovered that Enumerable::Enumerator already exists
(require 'enumerator'), but I can't find docs on it. ruby-doc.org gives
me a 404 on the enumerator page, a google search turns up pages in
Japanese, and I can't find a file it's loaded from (and SCRIPT_LINES__
isn't populated when I require 'enumerator') which leads me to believe
it's written in C and thus I can't RTSL for docs.

Any pointers?

And again, I reply to myself. I don't know why I didn't think of this
the first time - Pickaxe has the documentation. And from what I can
see, Enumerable::Enumerator is basically just a way to enumerate using
methods other than each (like Hash#each_key), which means it's not even
close to the functionality provided by the code I wrote.

Hrm, I just discovered that Enumerable::Enumerator already exists
(require 'enumerator'), but I can't find docs on it. ruby-doc.org gives
me a 404 on the enumerator page

I believe James Britt said he had a copy of the docs but couldn't see the value in putting them up:

http://groups.google.com/group/comp.lang.ruby/msg/7502f37a8a2fbf46

I filed a complaint, maybe you should add one too. :frowning:

Any pointers?

I discussed enumerator in this Ruby Quiz summary:

http://www.rubyquiz.com/quiz50.html

Then daz was nice enough to point me to the docs:

http://groups.google.com/group/comp.lang.ruby/msg/46abfe771a1b0033

For what it's worth, I don't believe enumerator is what you built here. The library is used to switch any given iterator to each():

>> require "enumerator"
=> true
>> str = "Some data..."
=> "Some data..."
>> enum = str.enum_for(:each_byte)
=> #<Enumerable::Enumerator:0x31e348>
>> enum.map { |byte| byte.chr }
=> ["S", "o", "m", "e", " ", "d", "a", "t", "a", ".", ".", "."]

Hope that helps.

James Edward Gray II

···

On Oct 18, 2005, at 5:41 PM, Kevin Ballard wrote:

James Edward Gray II wrote:

For what it's worth, I don't believe enumerator is what you built
here.

Do you think what I built here is worth packaging up into a gem?