在类上动态定义单例方法,使用子类正确计算

时间:2015-01-02 22:59:53

标签: ruby minitest

我目前正在开发一个Minitest扩展程序,它使用before / after_suite回调包装Minitest::Runnable.run。 Minitest的工作方式是测试类继承自Minitest::Runnable。此外,describe块动态创建继承自其定义的测试类的类。所以

# Minitest::Spec inherits from Minitest::Runnable
class TestClass < Minitest::Spec
  describe 'a describe block' do
    it 'should say hi' do
      # test here
    end
  end
end

生成一个继承自name"a describe block"的TestClass的新类。这会在Minitest管道中向下移动并在每个类上调用Mintest::Runnable.run(例如,self在每种情况下最终会成为包括"a describe block"在内的每个类。供参考:

module Minitest::Runnable
  def self.run reporter, options = {}
    require 'rubygems'; require 'pry'; binding.pry
    filter = options[:filter] || '/./'
    filter = Regexp.new $1 if filter =~ /\/(.*)\//

    filtered_methods = self.runnable_methods.find_all { |m|
      filter === m || filter === "#{self}##{m}"
    }

    with_info_handler reporter do
      filtered_methods.each do |method_name|
        run_one_method self, method_name, reporter
      end
    end
  end
end

我的扩展程序旨在让您拥有一个before_suite方法来包装Minitest :: Runnable.run:

class TestClass < Minitest::Spec
  before_suite do
    # do something
  end

  describe 'a describe block' do
    it 'should say hi' do
      # test here
    end
  end
end

我已经完成了它,但是我遇到了继承问题。当我重新定义包装器方法时,我的新单例方法的接收器即使对于子类也是TestClass(例如甚至对于descibe块)。这导致我的测试无法找到。

以下是我目前在套件前的实现:

module Minitest::SuiteCallbacks
  def self.extended(base)
    base.class_eval do
      class << self
        def before_suite(&before_suite_proc)
          @before_suite_proc = before_suite_proc

          context = self
          original_singleton_run = method(:run)
          define_singleton_method :run do |*args, &block|
            # `self` here winds up being `TestClass` instead of the dynamic class
            # (e.g. self.name => "TestClass" instead of "a describe block")

            context.setup_before_suite
            original_singleton_run.call(*args, &block)
          end
        end
      end
    end
  end
end

这会抓取run的当前实现并将其包装起来。问题在于,当从动态"a describe block"子类的上下文调用它时,方法内的selfTestClass而不是动态类。

有什么想法我可以做些什么来解决这个问题?我的目标是能够动态地将任何方法包装在继承链中,并使其适用于所有子类。

提前致谢!

1 个答案:

答案 0 :(得分:1)

错误在于方法绑定。我要么必须unbind方法,要么打开单例类并通过UnboundMethod引用instance_method

def before_suite
  # ...
  original = instance_method(:run)
  class << self
    define_method :run do |*args, &block|
      # add decorated logic
      original.bind(self).call(*args, &block)
    end
  end
end

这是我最初的猜测,但错误来自堆栈的进一步下降(我使用after_suite再次包装run方法并忘记更改该实现)。如有疑问,请将其评论出来!