单元测试保护和保护的最佳方法是什么? Ruby 中的私有方法?

发布于 2024-07-07 17:58:50 字数 390 浏览 9 评论 0原文

使用标准 Ruby Test::Unit 框架在 Ruby 中对受保护方法和私有方法进行单元测试的最佳方法是什么?

我确信有人会大喊大叫并教条地断言“你应该只对公共方法进行单元测试;如果需要单元测试,它不应该是受保护或私有方法”,但我对此并不感兴趣。 我有几个出于良好和有效的原因而受保护或私有的方法,这些私有/受保护的方法相当复杂,并且类中的公共方法依赖于这些受保护/私有的方法正常运行,因此我需要一种方法来测试受保护/私有方法。

还有一件事......我通常将给定类的所有方法放在一个文件中,并将该类的单元测试放在另一个文件中。 理想情况下,我希望将“受保护和私有方法的单元测试”功能实现到单元测试文件中,而不是主源文件中,以便使主源文件尽可能简单明了。

What's the best way to unit test protected and private methods in Ruby, using the standard Ruby Test::Unit framework?

I'm sure somebody will pipe up and dogmatically assert that "you should only unit test public methods; if it needs unit testing, it shouldn't be a protected or private method", but I'm not really interested in debating that. I've got several methods that are protected or private for good and valid reasons, these private/protected methods are moderately complex, and the public methods in the class depend upon these protected/private methods functioning correctly, therefore I need a way to test the protected/private methods.

One more thing... I generally put all the methods for a given class in one file, and the unit tests for that class in another file. Ideally, I'd like all the magic to implement this "unit test of protected and private methods" functionality into the unit test file, not the main source file, in order to keep the main source file as simple and straightforward as possible.

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

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

发布评论

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

评论(16

心意如水 2024-07-14 17:58:50

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

myobject.send(:method_name, args)

这是 Ruby 的一个“功能”。 :)

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

You can bypass encapsulation with the send method:

myobject.send(:method_name, args)

This is a 'feature' of Ruby. :)

There was internal debate during Ruby 1.9 development which considered having send respect privacy and send! ignore it, but in the end nothing changed in Ruby 1.9. Ignore the comments below discussing send! and breaking things.

素罗衫 2024-07-14 17:58:50

如果您使用 RSpec,这是一种简单的方法:

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

Here's one easy way if you use RSpec:

before(:each) do
  MyClass.send(:public, *MyClass.protected_instance_methods)  
end
许仙没带伞 2024-07-14 17:58:50

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

如果您的原始类是这样定义的:

class MyClass

  private

  def foo
    true
  end
end

在您的测试文件中,只需执行以下操作:

class MyClass
  public :foo

end

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

public :foo, :bar

Just reopen the class in your test file, and redefine the method or methods as public. You don't have to redefine the guts of the method itself, just pass the symbol into the public call.

If you original class is defined like this:

class MyClass

  private

  def foo
    true
  end
end

In you test file, just do something like this:

class MyClass
  public :foo

end

You can pass multiple symbols to public if you want to expose more private methods.

public :foo, :bar
不乱于心 2024-07-14 17:58:50

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

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

您还可以考虑使用 send(),它还可以让您访问私有和受保护的方法(就像 James Baker 建议的那样)

或者,您可以修改测试对象的元类以使私有/受保护的方法仅针对该对象的公共方法。

    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

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

instance_eval() might help:

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

You can use it to access private methods and instance variables directly.

You could also consider using send(), which will also give you access to private and protected methods (like James Baker suggested)

Alternatively, you could modify the metaclass of your test object to make the private/protected methods public just for that object.

    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

This will let you call these methods without affecting other objects of that class.
You could reopen the class within your test directory and make them public for all the
instances within your test code, but that might affect your test of the public interface.

悲歌长辞 2024-07-14 17:58:50

我过去这样做的一种方法是:

class foo
  def public_method
    private_method
  end

private unless 'test' == Rails.env

  def private_method
    'private'
  end
end

One way I've done it in the past is:

class foo
  def public_method
    private_method
  end

private unless 'test' == Rails.env

  def private_method
    'private'
  end
end
梦里的微风 2024-07-14 17:58:50

我确信有人会大声说
教条地断言“你应该
仅对公共方法进行单元测试; 如果它
需要单元测试,它不应该是
受保护或私有方法”,但我
对辩论不太感兴趣
那个。

您还可以将它们重构为一个新对象,其中这些方法是公共的,并在原始类中私有地委托给它们。 这将允许您测试这些方法,而无需在您的规范中使用神奇的metaruby,同时保持它们的私密性。

我有几种方法
永久保护或私有
正当理由

这些正当理由是什么? 其他 OOP 语言根本不需要私有方法(我想到了smalltalk - 其中私有方法仅作为约定而存在)。

I'm sure somebody will pipe up and
dogmatically assert that "you should
only unit test public methods; if it
needs unit testing, it shouldn't be a
protected or private method", but I'm
not really interested in debating
that.

You could also refactor those into a new object in which those methods are public, and delegate to them privately in the original class. This will allow you to test the methods without magic metaruby in your specs while yet keeping them private.

I've got several methods that are
protected or private for good and
valid reasons

What are those valid reasons? Other OOP languages can get away without private methods at all (smalltalk comes to mind - where private methods only exist as a convention).

遥远的她 2024-07-14 17:58:50

与 @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

Similar to @WillSargent's response, here's what I've used in a describe block for the special case of testing some protected validators without needing to go through the heavyweight process of creating/updating them with FactoryGirl (and you could use private_instance_methods similarly):

  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
剩余の解释 2024-07-14 17:58:50

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

To make public all protected and private method for the described class, you can add the following to your spec_helper.rb and not having to touch any of your spec files.

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
自演自醉 2024-07-14 17:58:50

您可以“重新打开”该类并提供一种委托给私有方法的新方法:

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

You can "reopen" the class and provide a new method that delegates to the private one:

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
一直在等你来 2024-07-14 17:58:50

我可能倾向于使用instance_eval()。 然而,在了解 instance_eval() 之前,我会在单元测试文件中创建一个派生类。 然后我将私有方法设置为公共方法。

在下面的示例中,build_year_range 方法在 PublicationSearch::ISIQuery 类中是私有的。 仅出于测试目的派生一个新类允许我将方法设置为公共方法,因此可以直接测试。 同样,派生类公开了一个名为“result”的实例变量,该变量以前未公开过。

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

在我的单元测试中,我有一个测试用例,它实例化 MockISIQuery 类并直接测试 build_year_range() 方法。

I would probably lean toward using instance_eval(). Before I knew about instance_eval(), however, I would create a derived class in my unit test file. I would then set the private method(s) to be public.

In the example below, the build_year_range method is private in the PublicationSearch::ISIQuery class. Deriving a new class just for testing purposes allows me to set a method(s) to be public and, therefore, directly testable. Likewise, the derived class exposes an instance variable called 'result' that was previously not exposed.

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

In my unit test I have a test case which instantiates the MockISIQuery class and directly tests the build_year_range() method.

匿名的好友 2024-07-14 17:58:50

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

MyClass.send(:public, :method_name)

这里的“method_name”是私有方法。

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

assert_equal expected, MyClass.instance.method_name(params)

In Test::Unit framework can write,

MyClass.send(:public, :method_name)

Here "method_name" is private method.

& while calling this method can write,

assert_equal expected, MyClass.instance.method_name(params)
对不⑦ 2024-07-14 17:58:50

这是我使用的类的一般添加。 这比仅仅公开您正在测试的方法更像是一种霰弹枪,但在大多数情况下这并不重要,而且更具可读性。

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 中被破坏,因此不是推荐的解决方案。

Here is a general addition to Class which I use. It's a bit more shotgun than only making public the method you are testing, but in most cases it doesn't matter, and it's much more readable.

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

Using send to access protected/private methods is broken in 1.9, so is not a recommended solution.

魂ガ小子 2024-07-14 17:58:50

更正上面的最佳答案:在 Ruby 1.9.1 中,是 Object#send 发送所有消息,而 Object#public_send 尊重隐私。

To correct the top answer above: in Ruby 1.9.1, it's Object#send that sends all the messages, and Object#public_send that respects privacy.

不再见 2024-07-14 17:58:50

您可以使用单例方法来代替 obj.send。 你的代码中还有 3 行代码
测试类,不需要更改要测试的实际代码。

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

在测试用例中,只要您想测试 my_private_method,就可以使用 my_private_method_publicly

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

私有方法的 obj.send 在 1.9 中被 send! 取代,但后来 send! 再次被删除。 所以 obj.send 工作得很好。

Instead of obj.send you can use a singleton method. It’s 3 more lines of code in your
test class and requires no changes in the actual code to be tested.

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

In the test cases you then use my_private_method_publicly whenever you want to test my_private_method.

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

obj.send for private methods was replaced by send! in 1.9, but later send! was removed again. So obj.send works perfectly well.

昔梦 2024-07-14 17:58:50

为了做到这一点:

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

In order to do this:

disrespect_privacy @object do |p|
  assert p.private_method
end

You can implement this in your test_helper file:

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
雅心素梦 2024-07-14 17:58:50

我知道我参加聚会迟到了,但不要测试私有方法......我想不出这样做的理由。 公共可访问方法是在某处使用该私有方法,测试该公共方法以及导致使用该私有方法的各种场景。 有东西进去,有东西出来。 测试私有方法是一个很大的禁忌,它会让以后重构代码变得更加困难。 它们是私人的是有原因的。

I know I'm late to the party, but don't test private methods....I can't think of a reason to do this. A publicly accessible method is using that private method somewhere, test the public method and the variety of scenarios that would cause that private method to be used. Something goes in, something comes out. Testing private methods is a big no-no, and it makes it much harder to refactor your code later. They are private for a reason.

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