在Ruby中unit testing受保护和私有方法的最佳方法是什么?

在Ruby中使用标准的Ruby Test::Unit框架来testing受保护和私有方法的最佳方式是什么?

我敢肯定,有人会提出并教条主张“你只应该unit testing公共方法;如果需要unit testing,它不应该是一个保护或私人方法”,但我没有真正有兴趣辩论。 我有几个保护或私有的方法,由于良好和有效的原因,这些私有/受保护的方法是相当复杂的,并且类中的公共方法依赖于这些保护/私有方法正常运行,因此我需要一种方法来testing受保护/私有方法。

还有一件事…我通常把给定类的所有方法放在一个文件中,单元在另一个文件中testing这个类。 理想情况下,为了保持主源文件尽可能简单直接,我希望所有的魔法都能将这个“受保护和私有方法的unit testing”function实现到unit testing文件中,而不是主要的源文件中。

您可以使用send方法绕过封装:

 myobject.send(:method_name, args) 

这是Ruby的一个“function”。 🙂

在Ruby 1.9开发过程中有内部争议,认为有send尊重隐私和send! 忽略它,但最终Ruby 1.9中没有任何改变。 忽略下面讨论send!的意见send! 打破事情

如果你使用RSpec,这是一个简单的方法:

 before(:each) do MyClass.send(:public, *MyClass.protected_instance_methods) end 

只要在testing文件中重新打开该类,并将该方法或方法重新定义为公共。 您不必重新定义方法本身的内容,只需将符号传递给public调用即可。

如果你原来的类是这样定义的:

 class MyClass private def foo true end end 

在你testing文件中,只要做这样的事情:

 class MyClass public :foo end 

如果您想公开更多的私有方法,您可以将多个符号传递给public

 public :foo, :bar 

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's 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 

您可以使用它直接访问私有方法和实例variables。

你也可以考虑使用send() ,这也会让你访问私有和受保护的方法(如James Baker所build议的)

或者,您可以修改您的testing对象的元类,使私有/受保护的方法公开为该对象。

  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 

这会让你调用这些方法而不影响该类的其他对象。 您可以在testing目录中重新打开该类,并将其公开于testing代码中的所有实例,但这可能会影响您对公共接口的testing。

我过去做过的一个方法是:

 class foo def public_method private_method end private unless 'test' == Rails.env def private_method 'private' end end 

我敢肯定,有人会提出并教条主张“你只应该unit testing公共方法;如果需要unit testing,它不应该是一个保护或私人方法”,但我没有真正有兴趣辩论。

你也可以将这些方法重构成一个新的对象,在这个对象中这些方法是公开的,并且在原始类中私下委托给它们。 这将允许你在你的规格中testing没有魔术符的方法,同时保持它们的私密性。

我有几个方法是保护或私人的,为了良好和有效的原因

那些有效的原因是什么? 其他的OOP语言可以在没有私有方法的情况下离开(smalltalk想到 – 私有方法只是作为一个约定存在)。

为了公开所有受保护的私有方法,您可以将以下内容添加到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 

我可能倾向于使用instance_eval()。 在我知道instance_eval()之前,我会在unit testing文件中创build一个派生类。 然后我会设置私有方法是公开的。

在下面的示例中,build_year_range方法在PublicationSearch :: ISIQuery类中是私有的。 为了testing的目的而派生一个新的类允许我设置一个方法是公开的,因此可以直接testing。 同样,派生类暴露了之前未公开的名为“result”的实例variables。

 # A derived class useful for testing. class MockISIQuery < PublicationSearch::ISIQuery attr_accessor :result public :build_year_range end 

在我的unit testing中,我有一个实例化MockISIQuery类的testing用例,并直接testingbuild_year_range()方法。

您可以“重新打开”class级,并提供一种委托给私人class级的新方法:

 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 

在Test :: Unit框架中可以写,

 MyClass.send(:public, :method_name) 

这里“method_name”是私有方法。

在调用这个方法的时候可以写,

 assert_equal expected, MyClass.instance.method_name(params) 

类似于@WillSargent的回应,下面是我在一个describe块中用于testing一些受保护的validation器的特殊情况,而不需要经过用FactoryGirl创build/更新它们的重量级过程(也可以使用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 

这是我使用的类的一个普通的补充。 霰弹枪比公开你所testing的方法要多一点,但在大多数情况下,这并不重要,而且它更具可读性。

 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 

使用发送访问受保护/私有方法在1.9中打破,所以不是推荐的解决scheme。

为了更正上面的答案:在Ruby 1.9.1中,发送所有消息的Object#发送,以及尊重隐私的Object#public_send。

我知道我迟到了,但不要testing私人的方法….我想不出一个理由去做这件事。 一个可公开访问的方法是在某个地方使用该私有方法,testing公共方法以及可能导致该私有方法被使用的各种场景。 有东西进来,东西出来了。 testing私有方法是一个不容忽视的问题,以后重构代码会变得更加困难。 他们是私人的原因。

而不是obj.send你可以使用单例方法。 在你的testing类中有三行代码,并且不需要修改要testing的代码。

 def obj.my_private_method_publicly (*args) my_private_method(*args) end 

在testing用例中,只要您想testingmy_private_method就可以使用my_private_method

http://mathandprogramming.blogspot.com/2010/01/ruby-testing-private-methods.html

obj.send私有方法被send!所取代send! 在1.9,但后来send! 被再次移除。 所以obj.send完美地工作。

为此:

 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