Examining a method

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? :slight_smile: Ahhh, Ruby!

Oops, forgot the output…

expects obj.+ (5) to return object like
expects obj.== (2) to return object like
expects nothing
expects obj.foo (bar) to yield objects like
expects obj.to_s () to return object like
expects nothing
and to return object like
expects nothing

Chris