Ruby에서 보호 및 개인 메소드를 단위 테스트하는 가장 좋은 방법은 무엇입니까?
표준 Ruby Test::Unit
프레임 워크를 사용하여 Ruby에서 보호 및 개인 메소드를 단위 테스트하는 가장 좋은 방법은 무엇입니까 ?
누군가 공개적으로 분석 할 때는 "공용 메소드 만 단위 테스트해야합니다. 단위 테스트가 필요한 경우 보호되거나 개인용 메소드가 아니어야합니다"라고 확신하지만 실제로는 그 논쟁에 관심이 없습니다. 나는 여러 가지 방법을 가지고 있습니다 보호 또는 좋은 유효한 이유로 개인을, 그러므로 내가 테스트하는 방법이 필요합니다, 이러한 개인 / 보호 방법은 적당히 복잡하고, 클래스의 public 메소드가 제대로 작동이 보호 / 개인 방법에 의존 보호 / 비공개 방법
한가지 더 ... 나는 일반적으로 주어진 클래스에 대한 모든 메소드를 하나의 파일에 넣고, 유닛은 그 클래스에 대한 다른 파일을 테스트합니다. 이상적으로는 주 소스 파일을 가능한 한 간단하고 간단하게 유지하기 위해이 "보호 및 개인 메소드의 단위 테스트"기능을 주 소스 파일이 아닌 단위 테스트 파일에 구현하는 것이 가장 좋습니다.
send 메소드를 사용하여 캡슐화를 우회 할 수 있습니다.
myobject.send(:method_name, args)
이것은 루비의 '기능'입니다. :)
루비 1.9 개발 과정에서 send
프라이버시 를 존중하고 send!
무시하는 내부 토론이 있었지만, 결국 루비 1.9에서는 아무런 변화가 없었습니다. 아래 내용을 논의 send!
하고 깨뜨리는 설명은 무시하십시오 .
RSpec을 사용하는 쉬운 방법은 다음과 같습니다.
before(:each) do
MyClass.send(:public, *MyClass.protected_instance_methods)
end
테스트 파일에서 클래스를 다시 열고 메소드를 공개로 다시 정의하십시오. 메소드 자체의 내장을 재정의 할 필요는 없으며 심볼을 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가 제안한 것처럼 개인 및 보호 된 메소드에 액세스 할 수있는을 사용 하는 것도 고려할 수 있습니다.
또는 테스트 개체의 메타 클래스를 수정하여 해당 개체에 대해서만 개인 / 보호 된 메서드를 공개 할 수 있습니다.
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.
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
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).
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
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
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
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.
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.
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)
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.
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.
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.
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
'development' 카테고리의 다른 글
활성 화면 크기를 얻으려면 어떻게해야합니까? (0) | 2020.06.30 |
---|---|
주어진 스키마에 테이블이 있는지 확인하는 방법 (0) | 2020.06.30 |
"캐시의 키 저장?" (0) | 2020.06.30 |
힘내 숨김 : "더러운 작업 트리에는 적용 할 수 없습니다. 변경 사항을 준비하십시오" (0) | 2020.06.30 |
왜 ~ True가 -2가됩니까? (0) | 2020.06.30 |