在 Ruby 中对类/方法进行非猴子修补

发布于 2024-12-12 00:58:37 字数 426 浏览 0 评论 0原文

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

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

问题是我使用 rcov 来运行整个事情,因为它使用 File.open 写入代码覆盖率信息,它获取猴子补丁版本而不是真正的版本。 如何取消此方法的monkeypatch以将其恢复为原始方法?我尝试过使用alias,但到目前为止没有效果。

I'm trying to unit test a piece of code that I've written in Ruby that calls File.open. To mock it out, I monkeypatched File.open to the following:

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

The problem is that I'm using rcov to run this whole thing since it uses File.open to write code coverage information, it gets the monkeypatched version instead of the real one. How can I un-monkeypatch this method to revert it to it's original method? I've tried messing around with alias, but to no avail so far.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

弄潮 2024-12-19 00:58:37

扩展@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

Expanding on @Tilo's answer, use alias again to undo the monkey patching.

Example:

# 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
柳若烟 2024-12-19 00:58:37

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

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

or you can use a stubbing framework (like rspec or mocha) and stub the File.Open method.

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

╰ゝ天使的微笑 2024-12-19 00:58:37

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

File is just a constant that holds an instance of Class. You could set it to a temporary class that responds to open, and then restore the original one:

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
寻找我们的幸福 2024-12-19 00:58:37

您可以简单地像这样别名:

alias new_name old_name

例如:

class File
  alias old_open open

  def open
    ...
  end
end

现在您仍然可以通过 File.old_open 访问原始 File.open 方法


或者,您可以尝试这样的操作:

ruby - 覆盖方法然后恢复

http://blog.jayfields.com/2006/12/ruby-别名-方法-alternative.html

you can simply alias it like this:

alias new_name old_name

e.g.:

class File
  alias old_open open

  def open
    ...
  end
end

now you can still access the original File.open method via File.old_open


Alternatively, you could try something like this:

ruby - override method and then revert

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

木森分化 2024-12-19 00:58:37

正确的方法是像 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

The correct way to do this is to actually use a stubbing framework like Dan says.

For example, in rspec, you'd do:

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

But for the curious, here's a semi-safe way to do it without external libs.
The idea is to isolate the stub so as little code is affected by it as possible.

I named this script 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

Demo:

> 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文