【问题标题】:Is it possible to run code after each line in Ruby?是否可以在 Ruby 中的每一行之后运行代码?
【发布时间】:2009-07-29 10:04:41
【问题描述】:

我知道在 ruby​​ 中可以使用 before 和 after 钩子来装饰方法,但是是否可以对给定方法的每一行都这样做?

例如,我有一个自动化测试,我想验证每个步骤之后页面上没有显示错误。错误显示为红色 div,Ruby 看不到 raise 或类似的东西,所以我必须手动检查它(还有其他几个用例)。

我知道使用set_trace_func 可能是可能的。但我认为这可能带来的问题多于好处,因为它适用于整个调用树(并且需要我自己过滤它)。

更新(澄清)

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

【问题讨论】:

    标签: ruby metaprogramming


    【解决方案1】:

    确实如此,您可以在The Ruby Programming Language 的第 8.9 节中获得有关如何执行此操作的完整说明。在每次调用该方法时运行代码涉及将方法发送到具有method_missing 实现的TracedObject 类。每当收到消息时,它都会调用method_missing 并执行您分配给它的任何代码。 (当然,这是用于一般跟踪)。

    这是对操作过程的一般描述,您可以查阅书籍了解详细信息。

    【讨论】:

    • 我的理解是 TracedObject (books.google.ru/books?id=jcUbTcr5XWwC&pg=PA286 ?) 可以拦截对给定类的所有方法的调用,但是如果我想拦截来自特定方法的所有调用呢?有问题的方法可能调用多个类,所以它本质上需要我将所有调用的类包装在 TracedObjects 中(这似乎作为一般解决方案是不可能的)?
    • 如果您对单个方法感兴趣,您只需过滤method_missing 内部的那个;但是您确实表示您不喜欢过滤的想法。您是对的,对于在多个类上发送消息的方法来说,这将是一个不雅且困难的解决方案。也许您应该编辑问题以添加第二个要求(关于几个类)。
    • 我对来自单一方法的呼出感兴趣,而不是对它的呼入。或者我想截取原方法的每一行,如原问题所述。
    • 哈,你要杀了我,但我认为你要找的东西可以在 8.11.3 中找到。的“Ruby 编程语言”,但您仍然面临必须在每个使用方法的类中设置跟踪的问题。至少,您可以将此作为解决方案的起点 --- 它会为您提供逐行跟踪。
    • 这很酷,我现在知道特征类是什么了。但它仍然需要我调用trace!在方法中使用的每个实例上。经过一番考虑,我想我会选择 set_trace_func —— 完成后我会发布一个示例作为回复。
    【解决方案2】:

    听起来你想跟踪 every 对象上的 every 方法调用,但只在每次调用 one 特殊的方法。在这种情况下,您只需重新定义打开和关闭仪器的方法。首先,使用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 方法,但它们应该非常简单:只需设置或取消设置一个类变量并在检测逻辑中检查它。

    【讨论】:

    • 如果我正确阅读了 8.11.3,那么就不应该有全局 TracedObject.install!可用,因为它不是 self.install!,它仅在实例级别可用。
    【解决方案3】:

    怎么样(只是尝试)

        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
    

    【讨论】:

      最近更新 更多