144

Test::Unit標準のRubyフレームワークを使用して、Rubyで保護されたプライベートメソッドを単体テストする最良の方法は何ですか?

誰かがパイプを鳴らして、「パブリック メソッドのみを単体テストする必要があります。単体テストが必要な場合は、保護されたメソッドやプライベート メソッドであってはなりません」と断言するでしょうが、私はそれについて議論することにあまり興味がありません。正当かつ正当な理由で保護またはプライベートなメソッドがいくつかあります。これらのプライベート/保護されたメソッドは適度に複雑であり、クラスのパブリック メソッドはこれらの保護/プライベート メソッドが正しく機能することに依存しているため、テストする方法が必要です。保護された/プライベート メソッド。

もう1つ...私は通常、特定のクラスのすべてのメソッドを1つのファイルに入れ、そのクラスの単体テストを別のファイルに入れます。理想的には、この「保護されたメソッドとプライベート メソッドの単体テスト」機能を、メイン ソース ファイルではなく、単体テスト ファイルに実装して、メイン ソース ファイルをできるだけシンプルかつ簡単に保つために、すべての魔法を使いたいと思います。

4

16 に答える 16

144

send メソッドでカプセル化をバイパスできます。

myobject.send(:method_name, args)

これは Ruby の「機能」です。:)

sendRuby 1.9 の開発中に、プライバシーを尊重してsend!無視することを検討する社内議論がありましたが、最終的に Ruby 1.9 では何も変わりませんでした。以下のコメントは無視してsend!ください。

于 2008-11-06T01:31:58.493 に答える
72

RSpec を使用する場合の簡単な方法の 1 つを次に示します。

before(:each) do
  MyClass.send(:public, *MyClass.protected_instance_methods)  
end
于 2008-11-06T17:01:32.470 に答える
33

テスト ファイルでクラスを再度開き、1 つまたは複数のメソッドを public として再定義します。メソッド自体の中身を再定義する必要はありません。シンボルをpublic呼び出しに渡すだけです。

元のクラスが次のように定義されている場合:

class MyClass

  private

  def foo
    true
  end
end

テストファイルで、次のようにします。

class MyClass
  public :foo

end

publicよりプライベートなメソッドを公開したい場合は、複数のシンボルを渡すことができます。

public :foo, :bar
于 2008-11-06T18:29:52.727 に答える
10

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

これにより、そのクラスの他のオブジェクトに影響を与えることなく、これらのメソッドを呼び出すことができます。テスト ディレクトリ内でクラスを再度開き、テスト コード内のすべてのインスタンスに対して公開することもできますが、公開インターフェイスのテストに影響を与える可能性があります。

于 2008-11-06T01:36:17.470 に答える
9

私が過去にそれを行った1つの方法は次のとおりです。

class foo
  def public_method
    private_method
  end

private unless 'test' == Rails.env

  def private_method
    'private'
  end
end
于 2008-11-06T14:48:23.703 に答える
8

誰かが「パブリック メソッドのみを単体テストする必要があります。単体テストが必要な場合は、保護されたメソッドやプライベート メソッドであってはなりません」と独断的に主張する人がいると確信していますが、私はそれについて議論することにあまり興味がありません。

また、これらのメソッドがパブリックである新しいオブジェクトにリファクタリングし、元のクラスでプライベートにデリゲートすることもできます。これにより、spec に魔法の metaruby を使用せずに、メソッドを非公開に保ちながらテストすることができます。

正当かつ正当な理由により、保護または非公開のメソッドがいくつかあります

それらの正当な理由は何ですか?他の OOP 言語は、プライベート メソッドがまったくなくても問題を解決できます (プライベート メソッドが規則としてのみ存在する場合、smalltalk が思い浮かびます)。

于 2009-07-18T01:19:38.493 に答える
6

@WillSargentの応答と同様に、describeFactoryGirlでそれらを作成/更新するという重いプロセスを経る必要なく、いくつかの保護されたバリデーターをテストする特別なケースのためにブロックで使用したものを次に示します(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
于 2016-03-03T21:41:06.993 に答える
5

記述されたクラスのすべてのプロテクト メソッドとプライベート メソッドをパブリックにするために、spec_helper.rb に次を追加できます。spec ファイルに手を加える必要はありません。

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
于 2012-11-25T14:28:59.780 に答える
4

クラスを「再開」して、プライベート メソッドに委譲する新しいメソッドを提供できます。

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
于 2008-11-06T15:15:18.737 に答える
2

Test::Unit フレームワークでは、次のように記述できます。

MyClass.send(:public, :method_name)

ここで、"method_name" はプライベート メソッドです。

& このメソッドを呼び出している間、書き込むことができます。

assert_equal expected, MyClass.instance.method_name(params)
于 2015-05-14T18:10:49.973 に答える
2

おそらく、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() メソッドを直接テストするテスト ケースがあります。

于 2008-11-06T14:54:16.110 に答える
1

これは、私が使用するクラスへの一般的な追加です。これは、テストしているメソッドを公開するだけというよりも、ちょっとしたことですが、ほとんどの場合、問題ではなく、はるかに読みやすくなっています。

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 では機能しないため、推奨される解決策ではありません。

于 2008-11-25T09:04:03.917 に答える
1

上記の一番上の回答を修正するには: Ruby 1.9.1 では、すべてのメッセージを送信するのは Object#send であり、プライバシーを尊重するのは Object#public_send です。

于 2010-03-01T22:22:25.110 に答える
1

obj.send の代わりに、シングルトン メソッドを使用できます。これは、テスト クラスにさらに 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.sendfor private Methods はsend!1.9 で置き換えられましたが、後でsend!再び削除されました。だからobj.send完全にうまく機能します。

于 2010-01-06T06:12:07.343 に答える
0

私はパーティーに遅れていることを知っていますが、プライベートメソッドをテストしません....これを行う理由が思いつきません。パブリックにアクセス可能なメソッドは、そのプライベート メソッドをどこかで使用しています。パブリック メソッドと、そのプライベート メソッドが使用される原因となるさまざまなシナリオをテストします。何かが入り、何かが出てくる。プライベート メソッドをテストすることは絶対に禁物であり、後でコードをリファクタリングすることが非常に難しくなります。彼らは理由でプライベートです。

于 2013-02-16T23:38:31.087 に答える