Какой лучший способ для unit test защищенных и приватных методов в Ruby?
Какой лучший способ для unit test защищенных и приватных методов в Ruby, используя стандартную структуру Ruby Test::Unit
?
Я уверен, что кто-то будет обсуждать и догматически утверждать, что "вы должны использовать только unit test общедоступные методы, а если это требует модульного тестирования, это не должен быть защищенный или закрытый метод", но мне неинтересно в обсуждении этого. У меня есть несколько методов, которые являются защищенными или приватными по хорошим и обоснованным причинам, эти частные/защищенные методы являются умеренно сложными, а общедоступные методы в классе зависят от этих защищенных/частных методов, поэтому мне нужен способ проверить защищенные/частные методы.
Еще одна вещь... Обычно я помещаю все методы для данного класса в один файл, а модуль тестирует этот класс в другом файле. В идеале я хотел бы, чтобы вся магия реализовала эту функциональность "unit test защищенных и частных методов" в файле unit test, а не в основном исходном файле, чтобы сохранить основной исходный файл максимально простым и понятным.
Ответы
Ответ 1
Вы можете обходить инкапсуляцию с помощью метода отправки:
myobject.send(:method_name, args)
Это "особенность" Ruby.:)
В ходе разработки Ruby 1.9 произошли внутренние споры, которые считали, что send
уважают конфиденциальность, а send!
игнорируют его, но в итоге в Ruby 1.9 ничего не изменилось. Игнорируйте приведенные ниже комментарии send!
и сломайте вещи.
Ответ 2
Здесь один простой способ, если вы используете RSpec:
before(:each) do
MyClass.send(:public, *MyClass.protected_instance_methods)
end
Ответ 3
Просто заново откройте класс в тестовом файле и переопределите метод или методы как общедоступные. Вам не нужно переопределять кишки самого метода, просто передайте символ в вызов public
.
Если исходный класс определен следующим образом:
class MyClass
private
def foo
true
end
end
В тестовом файле просто сделайте что-то вроде этого:
class MyClass
public :foo
end
Вы можете передать несколько символов в public
, если вы хотите открыть более частные методы.
public :foo, :bar
Ответ 4
instance_eval()
может помочь:
--------------------------------------------------- Object#instance_eval
obj.instance_eval(string [, filename [, lineno]] ) => obj
obj.instance_eval {| | block } => obj
------------------------------------------------------------------------
Evaluates a string containing Ruby source code, or the given
block, within the context of the receiver (obj). In order to set
the context, the variable self is set to obj while the code is
executing, giving the code access to obj instance variables. In
the version of instance_eval that takes a String, the optional
second and third parameters supply a filename and starting line
number that are used when reporting compilation errors.
class Klass
def initialize
@secret = 99
end
end
k = Klass.new
k.instance_eval { @secret } #=> 99
Вы можете использовать его для непосредственного доступа к приватным методам и переменным экземпляра.
Вы также можете рассмотреть возможность использования send()
, который также даст вам доступ к закрытым и защищенным методам (например, предложил Джеймс Бейкер)
В качестве альтернативы вы можете изменить метакласс вашего тестового объекта, чтобы сделать частные/защищенные методы общедоступными только для этого объекта.
test_obj.a_private_method(...) #=> raises NoMethodError
test_obj.a_protected_method(...) #=> raises NoMethodError
class << test_obj
public :a_private_method, :a_protected_method
end
test_obj.a_private_method(...) # executes
test_obj.a_protected_method(...) # executes
other_test_obj = test.obj.class.new
other_test_obj.a_private_method(...) #=> raises NoMethodError
other_test_obj.a_protected_method(...) #=> raises NoMethodError
Это позволит вам вызвать эти методы, не затрагивая другие объекты этого класса.
Вы можете повторно открыть класс в своем тестовом каталоге и сделать его общедоступным для всех
экземпляры в вашем тестовом коде, но это может повлиять на проверку открытого интерфейса.
Ответ 5
Один из способов, которые я сделал в прошлом, - это:
class foo
def public_method
private_method
end
private unless 'test' == Rails.env
def private_method
'private'
end
end
Ответ 6
Я уверен, что кто- догматически утверждают, что "вы должны только unit test общедоступные методы; если оно требует модульного тестирования, это не должно быть защищенный или закрытый метод", но я не очень заинтересованы в обсуждении что.
Вы также можете реорганизовать их в новый объект, в котором эти методы являются общедоступными, и делегировать их конфиденциально в исходном классе. Это позволит вам тестировать методы без волшебной метарубы в ваших спецификациях, но при этом сохраняя их конфиденциальность.
У меня есть несколько методов, которые защищенными или частными для действительные причины
Каковы эти веские причины? Другие языки ООП могут уйти без частных методов вообще (smalltalk приходит на ум - там, где частные методы существуют только как конвенция).
Ответ 7
Чтобы сделать общедоступным весь защищенный и закрытый метод для описанного класса, вы можете добавить следующее в свой spec_helper.rb и не прикасаться ни к одному из ваших файлов спецификаций.
RSpec.configure do |config|
config.before(:each) do
described_class.send(:public, *described_class.protected_instance_methods)
described_class.send(:public, *described_class.private_instance_methods)
end
end
Ответ 8
Вероятно, я склоняюсь к использованию instance_eval(). Однако, прежде чем я узнал о instance_eval(), я бы создал производный класс в моем файле unit test. Затем я бы включил приватный метод (ы).
В приведенном ниже примере метод build_year_range является приватным в классе PublicationSearch:: ISIQuery. Вывод нового класса только для целей тестирования позволяет мне установить метод как общедоступный и, следовательно, непосредственно проверяемый. Аналогично, производный класс предоставляет переменную экземпляра, называемую "результатом", которая ранее не была показана.
# A derived class useful for testing.
class MockISIQuery < PublicationSearch::ISIQuery
attr_accessor :result
public :build_year_range
end
В моем unit test у меня есть тестовый пример, который создает экземпляр класса MockISIQuery и непосредственно проверяет метод build_year_range().
Ответ 9
Вы можете "повторно открыть" класс и предоставить новый метод, который делегирует частному:
class Foo
private
def bar; puts "Oi! how did you reach me??"; end
end
# and then
class Foo
def ah_hah; bar; end
end
# then
Foo.new.ah_hah
Ответ 10
В модуле Test:: Unit можно писать,
MyClass.send(:public, :method_name)
Здесь "method_name" - частный метод.
& при вызове этого метода можно написать
assert_equal expected, MyClass.instance.method_name(params)
Ответ 11
Подобно ответу @WillSargent, вот что я использовал в блоке describe
для специального случая тестирования некоторых защищенных валидаторов без необходимости проходить через тяжеловесный процесс создания/обновления с помощью FactoryGirl (и вы могли бы использовать private_instance_methods
аналогично):
describe "protected custom `validates` methods" do
# Test these methods directly to avoid needing FactoryGirl.create
# to trigger before_create, etc.
before(:all) do
@protected_methods = MyClass.protected_instance_methods
MyClass.send(:public, *@protected_methods)
end
after(:all) do
MyClass.send(:protected, *@protected_methods)
@protected_methods = nil
end
# ...do some tests...
end
Ответ 12
Вот общее дополнение к классу, который я использую. Это немного больше дробовика, чем обнародовать метод, который вы тестируете, но в большинстве случаев это не имеет значения, и это гораздо более читаемо.
class Class
def publicize_methods
saved_private_instance_methods = self.private_instance_methods
self.class_eval { public *saved_private_instance_methods }
begin
yield
ensure
self.class_eval { private *saved_private_instance_methods }
end
end
end
MyClass.publicize_methods do
assert_equal 10, MyClass.new.secret_private_method
end
Использование метода send для доступа к защищенным/закрытым методам нарушено в 1.9, поэтому это не рекомендуется.
Ответ 13
Чтобы исправить верхний ответ выше: в Ruby 1.9.1 отправляется Object # send, который отправляет все сообщения, и Object # public_send, которые уважают конфиденциальность.
Ответ 14
Я знаю, что опаздываю на вечеринку, но не проверяю частные методы... Я не могу придумать причину этого. Общедоступный метод использует этот частный метод где-то, проверяет общедоступный метод и множество сценариев, которые могли бы использовать этот частный метод. Что-то происходит, что-то выходит. Тестирование частных методов - это большой нет-нет, и это значительно усложняет реорганизацию вашего кода позже. Они являются частными по какой-то причине.
Ответ 15
Вместо obj.send вы можете использовать метод singleton. Его еще 3 строки кода в вашем
и не требует изменений в фактическом тестируемом коде.
def obj.my_private_method_publicly (*args)
my_private_method(*args)
end
В тестовых случаях вы затем используете my_private_method_publicly
, когда хотите протестировать my_private_method
.
http://mathandprogramming.blogspot.com/2010/01/ruby-testing-private-methods.html
obj.send
для частных методов был заменен на send!
в 1.9, но позже send!
снова был удален. Итак, obj.send
работает отлично.
Ответ 16
Чтобы сделать это:
disrespect_privacy @object do |p|
assert p.private_method
end
Вы можете реализовать это в своем файле test_helper:
class ActiveSupport::TestCase
def disrespect_privacy(object_or_class, &block) # access private methods in a block
raise ArgumentError, 'Block must be specified' unless block_given?
yield Disrespect.new(object_or_class)
end
class Disrespect
def initialize(object_or_class)
@object = object_or_class
end
def method_missing(method, *args)
@object.send(method, *args)
end
end
end