Hello,
I was thinking recently about how to examine an interface or, more simply, a
single method. I don’t know if anyone has tried something like this before,
but I wanted to make an object which I could pass into a method and see what
that method does to it to get an idea of what that method was expecting.
I thought that maybe I should delete most of the methods it gets from Object
(or Kernel), and then rewrite the ones I didn’t delete to keep track of
those method calls.
Here’s what I have so far:
class Expectation
(public_instance_methods(true) +
protected_instance_methods(true) +
private_instance_methods(true)).each do |meth|
next if meth == 'initialize’
next if meth == 'send’
next if meth == 'id’
undef_method meth
end
(public_instance_methods(true) +
protected_instance_methods(true) +
private_instance_methods(true)).each do |meth|
module_eval <<-END_DEFINITION
def #{meth} (*args, &block)
method_missing :#{meth}, *args, &block
super *args, &block
end
END_DEFINITION
end
def initialize (*args, &block)
if !@calls
@calls = {}
@creator = args[0]
@depth = (nil == @creator) ? 0 : args[1]
else
method_missing :initialize, *args, &block
end
end
def inspect (*args, &block)
# I don't want to keep track of `inspect' calls.
return ' '*@depth + "expects nothing\n" if @calls.empty?
str = ''
@calls.each do |meth, calls|
calls.each do |call|
str += ' '*@depth
str += "expects obj.#{meth} (#{call[0].join(', ')}) "
if call[2]
str += "to yield objects like\n"
call[3].each do |blockParam|
str += blockParam.inspect
end
str += ' '*@depth + 'and '
end
str += "to return object like\n"
str += call[1].inspect
end
end
str
end
def method_missing symbol, *args, &block
# I don't see how I could keep track of `method_missing' calls.
@calls[symbol] = [] if !@calls[symbol]
retVal = Expectation.new (self, @depth+1)
if block
blockParams = []
block.arity.abs.times do
blockParams << Expectation.new(self, @depth+1)
end
block.call(*blockParams)
@calls[symbol] << [args, retVal, block, blockParams]
else
@calls[symbol] << [args, retVal]
end
retVal
end
end
puts
expect = Expectation.new
expect+5 == 2
expect.foo(‘bar’){|x| puts x}
p expect
Thoughts? Suggestions? Reasons why this is a terrible idea and I should
stop now?
I think something like this could possibly be useful for trying to determine
exactly what an interface does. In a sense, it’s like what you do when
you’re building mock objects for unit tests, except that it sort of builds
itself as you use it.
It would be much nicer if the output was more readable… any suggestions?
Improvements?
Oh brave new world, that has such programs in it…
Chris
PS: Have I mentioned how much I love this language? Ahhh, Ruby!