在Ruby中修补类/方法的猴子

时间:2011-10-25 16:27:49

标签: ruby testing monkeypatching rcov

我正在尝试对我用Ruby编写的一段调用File.open的代码进行单元测试。为了模仿它,我将File.open monkeypatched到以下内容:

class File
  def self.open(name, &block)
    if name.include?("retval")
      return "0\n"
    else
      return "1\n"
    end
  end
end

问题是我使用rcov来运行整个事情,因为它使用File.open来编写代码覆盖率信息,它获得了monkeypatched版本而不是真实版本。 如何解除此单一方法以将其恢复为原始方法?我已尝试使用alias,但到目前为止无济于事。

5 个答案:

答案 0 :(得分:12)

扩展@Tilo的答案,再次使用别名撤消猴子修补。

示例:

# Original definition
class Foo
  def one()
    1
  end
end

foo = Foo.new
foo.one

# Monkey patch to 2
class Foo
  alias old_one one
  def one()
    2
  end
end

foo.one

# Revert monkey patch
class Foo
  alias one old_one
end

foo.one

答案 1 :(得分:2)

File只是一个包含Class实例的常量。您可以将其设置为响应open的临时类,然后恢复原始类:

original_file = File
begin
  File = Class.new             # warning: already initialized constant File
  def File.open(name, &block)
    # Implement method
  end
  # Run test
ensure
  File = original_file         # warning: already initialized constant File
end

答案 2 :(得分:2)

或者您可以使用存根框架(如rspec或mocha)并存根File.Open方法。

File.stub(:open =>“0 \ n”)

答案 3 :(得分:2)

你可以简单地将其命名为:

alias new_name old_name

e.g:

class File
  alias old_open open

  def open
    ...
  end
end

现在您仍然可以通过File.old_open

访问原始的File.open方法

或者,您可以尝试这样的事情:

ruby - override method and then revert

http://blog.jayfields.com/2006/12/ruby-alias-method-alternative.html

答案 4 :(得分:1)

这样做的正确方法是实际使用像Dan说的那样的存根框架。

例如,在rspec中,您可以:

it "reads the file contents" do
  File.should_receive(:open) {|name|
    if name.include?("retval")
      "0\n"
    else
      "1\n"
    end
  }
  File.open("foo retval").should == "0\n"
  File.open("other file").should == "1\n"
end

但是对于好奇的人来说,这是一种没有外部库的半安全方式。 我们的想法是隔离存根,以便尽可能少地影响它。

我将此脚本命名为isolated-patch.rb

class File
  class << self
    alias_method :orig_open, :open

    def stubbed_open(name, &block)
      if name.include?("retval")
        return "0\n"
      else
        return "1\n"
      end
    end
  end
end


def get_size(path)
  # contrived example
  File.open(path) {|fh|
    # change bytesize to size on ruby 1.8
    fh.read.bytesize
  }
end

# The stub will apply for the duration of the block.
# That means the block shouldn't be calling any methods where file opening
#   needs to work normally.
# Warning: not thread-safe
def stub_file_open
  class << File
    alias_method :open, :stubbed_open
  end
  yield
ensure
  class << File
    alias_method :open, :orig_open
  end
end

if __FILE__ == $0
  stub_file_open do
    val = File.open("what-retval") { puts "this is not run" }
    puts "stubbed open() returned: #{val.inspect}"
    size = get_size("test.txt")
    puts "obviously wrong size of test.txt: #{size.inspect}"
  end

  # you need to manually create this file before running the script
  size = get_size("test.txt")
  puts "size of test.txt: #{size.inspect}"
end

演示:

> echo 'foo bar' > test.txt
> ruby isolated-patch.rb
stubbed open() returned: "0\n"
obviously wrong size of test.txt: "1\n"
size of test.txt: 8