Un-Monkey, исправляющий класс / метод в Ruby - PullRequest
8 голосов
/ 25 октября 2011

Я пытаюсь выполнить модульное тестирование фрагмента кода, написанного на 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 для записи информации о покрытии кода, он получает версию с monkeypatched вместо реальной. Как мне отменить использование этого метода, чтобы вернуть его к исходному методу? Я пытался возиться с alias, но пока безрезультатно.

Ответы [ 5 ]

12 голосов
/ 26 октября 2011

Расширяя ответ @ 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
2 голосов
/ 25 октября 2011

или вы можете использовать среду-заглушку (например, rspec или mocha) и заглушить метод File.Open.

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

2 голосов
/ 25 октября 2011

Вы можете просто использовать псевдоним это так:

alias new_name old_name

например:.

class File
  alias old_open open

  def open
    ...
  end
end

теперь вы можете получить доступ к оригинальному методу File.open через File.old_open


В качестве альтернативы, вы можете попробовать что-то вроде этого:

ruby ​​- переопределить метод и затем вернуть

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

2 голосов
/ 25 октября 2011

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
1 голос
/ 28 сентября 2012

Правильный способ сделать это - использовать структуру-заглушку, как говорит Дэн.

Например, в 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
...