是否可以在Ruby中的每一行之后运行代码?

时间:2009-07-29 10:04:41

标签: ruby metaprogramming

我知道可以用ruby中的钩子前后装饰方法,但是可以为给定方法的每一行做这个吗?

例如,我有一个自动化测试,我想验证每个步骤后页面上没有显示错误。错误显示为红色div,并且对于raise或类似的东西不可见,因此我必须手动检查它(还有其他一些用例)。

我知道可以使用set_trace_func。但我认为这可能带来更多问题而不是好处,因为它可以在整个调用树上运行(并且需要我自己过滤它)。

更新(澄清)

我有兴趣拦截在给定方法中执行的所有操作(或调用)。这意味着调用了未指定数量的类,因此我不能只截取任何给定的类/方法集。

3 个答案:

答案 0 :(得分:2)

是的,您可以在The Ruby Programming Language的第8.9节中详细了解如何操作。在每次调用方法时运行代码涉及将方法发送到具有TracedObject实现的method_missing类。每当收到消息时,它就会调用method_missing并执行您分配给它的任何代码。 (当然,这是一般的追踪)。

这是对此过程的一般描述,您可以参考本书了解详情。

答案 1 :(得分:2)

听起来你想跟踪每个对象上的每个方法调用,但仅限于每次调用一个 特定的方法。在这种情况下,您可以重新定义方法以打开和关闭仪器。首先,使用Pinochle's answer中提出的通用仪器,然后重新定义相关方法,如下所示:

# original definition, in /lib/foo.rb:
class Foo
  def bar(baz)
    do_stuff
  end
end

# redefinition, in /test/add_instrumentation_to_foo.rb:
Foo.class_eval do
  original_bar = instance_method(:bar)
  def bar(baz)
    TracedObject.install!
    original_bar.bind(self).call(baz)
    TracedObject.uninstall!
  end
end

您需要编写install!uninstall方法,但它们应该非常简单:只需设置或取消设置类变量并在检测逻辑中检查它。

答案 2 :(得分:0)

怎么样(只是尝试)

    class User

      def initialize(name)
        @name = name
      end

      def say_hello
        puts "hello #{@name}"
      end

      def say_hi(friend)
        puts "hi #{@name} from #{friend}"
      end

      def say_bye(a, b = 'Anna')
        puts "bye #{a} and #{b}"
      end

    end

    User.class_eval do
      User.instance_methods(false).each do |method|
        original = instance_method(method)
        define_method method do |*options| 
          parameters = original.parameters
          if parameters.empty?
            original.bind(self).call
          else
            original.bind(self).call(*options)
          end
          puts __method__
        end
      end
    end

    user = User.new("John")

    user.say_hello
    user.say_hi("Bob")
    user.say_bye("Bob")
    user.say_bye("Bob", "Lisa")

输出:

    hello John
    say_hello
    hi John from Bob
    say_hi
    bye Bob and Anna
    say_bye
    bye Bob and Lisa
    say_bye