Un-Monkey, исправляющий класс / метод в Ruby
Я пытаюсь выполнить модульное тестирование кода, который я написал в 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 ответов
Расширяя ответ @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
Или вы можете использовать среду-заглушку (например, rspec или mocha) и заглушить метод File.Open.
File.stub (: open => "0 \ n")
Вы можете просто псевдоним это так:
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
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
Правильный способ сделать это состоит в том, чтобы фактически использовать структуру окурка, как говорит Дэн.
Например, в 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