標準のRuby Test::Unit
フレームワークを使用して、Rubyのプロテクトメソッドとプライベートメソッドを単体テストする最良の方法は何ですか?
誰かがパイプして、「パブリックメソッドのみをユニットテストする必要があります。ユニットテストが必要な場合、保護されたメソッドまたはプライベートメソッドであってはなりません」と独断的に断言しますが、私はそれについて議論することにあまり興味がありません。正当かつ正当な理由でare protectedまたはprivateであるメソッドがいくつかあります。これらのprivate/protectedメソッドはやや複雑で、クラスのpublicメソッドはこれらのprotected/privateメソッドが正しく機能することに依存しています。したがって、保護/プライベートメソッドをテストする方法が必要です。
もう1つ...特定のクラスのすべてのメソッドを1つのファイルに入れ、そのクラスのユニットテストを別のファイルに入れます。理想的には、メインソースファイルを可能な限りシンプルで単純な状態に保つために、すべての魔法でこの「保護されたプライベートメソッドのユニットテスト」機能をメインソースファイルではなくユニットテストファイルに実装してください。
Sendメソッドでカプセル化をバイパスできます。
myobject.send(:method_name, args)
これはRubyの「機能」です。 :)
Ruby 1.9開発中にsend
がプライバシーを尊重し、send!
無視しますが、最終的にはRuby 1.9で変更されたものはありません。send!
と破壊的なもの。
RSpecを使用する場合の簡単な方法は次のとおりです。
before(:each) do
MyClass.send(:public, *MyClass.protected_instance_methods)
end
テストファイルでクラスを再度開き、1つまたは複数のメソッドをパブリックとして再定義します。メソッド自体の内部を再定義する必要はありません。シンボルをpublic
呼び出しに渡すだけです。
元のクラスが次のように定義されている場合:
class MyClass
private
def foo
true
end
end
テストファイルで、次のようにします。
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
_
これを使用して、プライベートメソッドとインスタンス変数に直接アクセスできます。
また、send()
の使用を検討することもできます。これにより、プライベートおよび保護されたメソッド(James Bakerが提案したように)にアクセスすることもできます。
または、テストオブジェクトのメタクラスを変更して、そのオブジェクト専用のprivate/protectedメソッドをパブリックにすることもできます。
_ 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
_
これにより、そのクラスの他のオブジェクトに影響を与えることなく、これらのメソッドを呼び出すことができます。テストディレクトリ内でクラスを再度開き、テストコード内のすべてのインスタンスに対してクラスをパブリックにすることができますが、パブリックインターフェイスのテストに影響する可能性があります。
私が過去にやったことの1つは次のとおりです。
class foo
def public_method
private_method
end
private unless 'test' == Rails.env
def private_method
'private'
end
end
私は誰かがパイプして、「パブリックメソッドのみをユニットテストする必要があります。ユニットテストが必要な場合、保護されたメソッドまたはプライベートメソッドであってはならない」と独断的に主張しますが、私はそれについて議論することにあまり興味がありません。
また、これらのメソッドがパブリックである新しいオブジェクトにリファクタリングし、元のクラスでプライベートに委任することもできます。これにより、仕様に魔法のメタルービィーを使用せずに、メソッドをプライベートに保ちながらテストできます。
正当かつ正当な理由で保護または非公開になっている方法がいくつかあります
それらの正当な理由は何ですか?その他OOP言語はプライベートメソッドなしで逃げることができます(Smalltalkが思い浮かびます-プライベートメソッドは慣例としてのみ存在します)。
記述されたクラスのすべてのprotectedおよびprivateメソッドを公開するには、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
@WillSargentの応答と同様に、ここでは、FactoryGirlで作成/更新する重いプロセスを経ることなく、いくつかの保護されたバリデータをテストする特別な場合にdescribe
ブロックで使用したものを使用します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
私はパーティーに遅れていることを知っていますが、プライベートメソッドをテストしないでください....これを行う理由を考えることはできません。パブリックにアクセス可能なメソッドは、そのプライベートメソッドをどこかで使用し、パブリックメソッドと、そのプライベートメソッドが使用される原因となるさまざまなシナリオをテストします。何かが入り、何かが出てきます。プライベートメソッドのテストは大したことではなく、後でコードをリファクタリングするのがはるかに難しくなります。理由はプライベートです。
クラスを「再オープン」して、プライベートメソッドに委任する新しいメソッドを提供できます。
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)
私はおそらくinstance_eval()を使用することに傾いています。ただし、instance_eval()を知る前に、ユニットテストファイルに派生クラスを作成しました。次に、プライベートメソッドをパブリックに設定します。
次の例では、build_year_rangeメソッドはPublicationSearch :: ISIQueryクラスでプライベートです。テストのためだけに新しいクラスを派生させることで、メソッドをパブリックに設定し、直接テストできるようにすることができます。同様に、派生クラスは、以前は公開されていなかった「結果」と呼ばれるインスタンス変数を公開します。
# A derived class useful for testing.
class MockISIQuery < PublicationSearch::ISIQuery
attr_accessor :result
public :build_year_range
end
ユニットテストには、MockISIQueryクラスをインスタンス化し、build_year_range()メソッドを直接テストするテストケースがあります。
ここに、私が使用するクラスへの一般的な追加を示します。テストしているメソッドを公開するだけではありませんが、ほとんどの場合、問題ではなく、はるかに読みやすくなっています。
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を使用してprotected/privateメソッドにアクセスしますis 1.9では壊れているため、推奨されるソリューションではありません。
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.send
は1.9でsend!
に置き換えられましたが、後でsend!
は再び削除されました。したがって、obj.send
は完全に機能します。
上記のトップの回答を修正するには、in Ruby 1.9.1、すべてのメッセージを送信するのはObject#sendで、プライバシーを尊重するのはObject#public_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