Unit testing an each function

P

Peter Hickman

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.
 
J

James Edward Gray II

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?

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
 
K

Kevin Ballard

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
 
P

Peter Hickman

Kevin said:
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.
 
K

Kevin Ballard

Peter said:
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?
 
P

Peter Hickman

Kevin said:
Peter Hickman wrote:



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.
 
J

James Edward Gray II

# 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
 
P

Peter Hickman

James said:
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.
 
J

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.

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. ;)

My two cents.

James Edward Gray II
 
K

Kevin Ballard

Peter said:
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.
 
K

Kevin Ballard

Kevin said:
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
 
K

Kevin Ballard

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).
 
K

Kevin Ballard

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?
 
R

Ryan Leavengood

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/11669710=
e085ca07

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
 
K

Kevin Ballard

Ryan said:
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.
 
K

Kevin Ballard

Kevin said:
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?
 
K

Kevin Ballard

Kevin said:
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.
 
J

James Edward Gray II

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. :(
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():
=> ["S", "o", "m", "e", " ", "d", "a", "t", "a", ".", ".", "."]

Hope that helps.

James Edward Gray II
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,181
Messages
2,570,970
Members
47,537
Latest member
BellCorone

Latest Threads

Top