रूबी में यूनिट टेस्ट संरक्षित और निजी तरीकों का सबसे अच्छा तरीका क्या है?


136

रूबी Test::Unitढांचे में यूनिट टेस्ट संरक्षित और निजी तरीकों का सबसे अच्छा तरीका क्या है, मानक रूबी ढांचे का उपयोग करना ?

मुझे यकीन है कि कोई व्यक्ति पाइप और हठधर्मिता पर जोर देगा कि "आपको केवल इकाई परीक्षण सार्वजनिक विधियों चाहिए; अगर इसे इकाई परीक्षण की आवश्यकता है, तो यह एक संरक्षित या निजी तरीका नहीं होना चाहिए", लेकिन मुझे वास्तव में उस पर बहस करने में कोई दिलचस्पी नहीं है। मुझे कई तरीके मिले हैं जो अच्छे और वैध कारणों से सुरक्षित या निजी हैं, ये निजी / संरक्षित विधियां मामूली जटिल हैं, और कक्षा में सार्वजनिक तरीके इन संरक्षित / निजी तरीकों पर सही ढंग से काम करते हैं, इसलिए मुझे परीक्षण करने का एक तरीका चाहिए संरक्षित / निजी तरीके।

एक और बात ... मैं आम तौर पर एक फ़ाइल में किसी दिए गए वर्ग के लिए सभी तरीकों को रखता हूं, और एक अन्य फ़ाइल में उस वर्ग के लिए इकाई परीक्षण। आदर्श रूप में, मैं मुख्य स्रोत फ़ाइल को सरल और सीधा रखने के लिए यूनिट टेस्ट फ़ाइल में, मुख्य स्रोत फ़ाइल के रूप में "संरक्षित और निजी तरीकों की" इकाई परीक्षण को लागू करने के लिए सभी जादू को पसंद करूंगा।


जवाबों:


135

आप भेजने की विधि के साथ इनकैप्सुलेशन को बायपास कर सकते हैं:

myobject.send(:method_name, args)

यह रूबी का 'फीचर' है। :)

रूबी 1.9 के विकास के दौरान आंतरिक बहस हुई, जिसे sendसम्मान की निजता माना गया और send!इसे अनदेखा किया गया, लेकिन अंत में रूबी 1.9 में कुछ भी नहीं बदला। send!चीजों पर चर्चा करने और तोड़ने के लिए नीचे टिप्पणी पर ध्यान न दें ।


मुझे लगता है कि इस उपयोग को 1.9
जीन टी

6
मुझे संदेह है कि वे इसे रद्द कर देंगे, क्योंकि वे तुरंत बड़ी संख्या में रूबी परियोजनाओं को तोड़ देंगे
ओरियन एडवर्ड्स

1
गहरे लाल रंग का 1.9 है बस सब कुछ के बारे में टूट गया।
jes5199

1
बस ध्यान दें करने के लिए: कोई बात नहीं send!बात है, यह बहुत पहले निरस्त किया गया है, send/__send__सभी दृश्यता के तरीकों कॉल कर सकते हैं - redmine.ruby-lang.org/repositories/revision/1?rev=13824
dolzenko

2
नहीं है public_send(प्रलेखन यहाँ ) यदि आप गोपनीयता का सम्मान करना चाहते हैं। मुझे लगता है कि रूबी 1.9 के लिए नया है।
एंड्रयू ग्रिम

71

यदि आप RSpec का उपयोग करते हैं तो यहां एक आसान तरीका है:

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

9
हां ये तो शानदार है। निजी तरीकों के लिए, उपयोग करें ... private_instance_methods बजाय safe_instance_methods
माइक Blyth

12
महत्वपूर्ण चेतावनी: यह आपके टेस्ट सूट निष्पादन के शेष के लिए इस वर्ग के तरीकों को सार्वजनिक करता है, जिसमें अप्रत्याशित दुष्प्रभाव हो सकते हैं! आप बाद में फिर से संरक्षित तरीकों के अनुसार तरीकों को फिर से परिभाषित करना चाहते हैं: (प्रत्येक) ब्लॉक या भविष्य में डरावना परीक्षण विफलताओं से पीड़ित।
पैथोजन

यह एक ही समय में भयानक और शानदार है
रॉबर्ट

मैंने इसे पहले कभी नहीं देखा है और मैं यह महसूस कर सकता हूं कि यह काल्पनिक रूप से काम करता है। हाँ, यह दोनों भयानक और शानदार हैं लेकिन जब तक आप इसे आपके द्वारा परीक्षण की जा रही विधि के स्तर पर स्कोप करते हैं, मैं यह तर्क दूंगा कि आपके पास अप्रत्याशित दुष्प्रभाव नहीं होंगे जो पैथोजन के लिए हैं।
फज़्यग्रुप

32

बस अपनी परीक्षण फ़ाइल में कक्षा को फिर से खोलें, और विधि या विधियों को सार्वजनिक रूप से फिर से परिभाषित करें। आपको स्वयं विधि की हिम्मत को फिर से परिभाषित करने की जरूरत नहीं है, बस प्रतीक को publicकॉल में पास करें।

यदि आप मूल वर्ग को इस तरह परिभाषित करते हैं:

class MyClass

  private

  def foo
    true
  end
end

आप परीक्षण फ़ाइल में, बस कुछ इस तरह से करें:

class MyClass
  public :foo

end

publicयदि आप अधिक निजी विधियों को उजागर करना चाहते हैं, तो आप कई प्रतीकों को पास कर सकते हैं ।

public :foo, :bar

2
यह मेरा पसंदीदा तरीका है क्योंकि यह आपके कोड को अछूता छोड़ देता है और बस विशिष्ट परीक्षण के लिए गोपनीयता को समायोजित करता है। अपने परीक्षणों को चलाने के बाद चीजों को वापस रखना न भूलें या आप बाद में परीक्षणों को भ्रष्ट कर सकते हैं।
ktec

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(), जो आपको निजी और संरक्षित तरीकों तक पहुंच देगा (जैसे जेम्स बेकर ने सुझाव दिया)

वैकल्पिक रूप से, आप उस वस्तु के लिए निजी / संरक्षित विधियों को सार्वजनिक करने के लिए अपने परीक्षण ऑब्जेक्ट के मेटाक्लस को संशोधित कर सकते हैं।

    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

यह आपको इन विधियों को उस वर्ग की अन्य वस्तुओं को प्रभावित किए बिना कॉल करने देगा। आप अपने परीक्षण निर्देशिका के भीतर कक्षा को फिर से खोल सकते हैं और उन्हें अपने परीक्षण कोड के भीतर सभी उदाहरणों के लिए सार्वजनिक कर सकते हैं, लेकिन यह सार्वजनिक इंटरफ़ेस के आपके परीक्षण को प्रभावित कर सकता है।


9

एक तरीका मैंने इसे अतीत में किया है:

class foo
  def public_method
    private_method
  end

private unless 'test' == Rails.env

  def private_method
    'private'
  end
end

8

मुझे यकीन है कि कोई व्यक्ति पाइप और हठधर्मिता पर जोर देगा कि "आपको केवल इकाई परीक्षण सार्वजनिक विधियों चाहिए; अगर इसे इकाई परीक्षण की आवश्यकता है, तो यह एक संरक्षित या निजी तरीका नहीं होना चाहिए", लेकिन मुझे वास्तव में उस पर बहस करने में कोई दिलचस्पी नहीं है।

आप उन्हें एक नई वस्तु में भी शामिल कर सकते हैं, जिसमें वे विधियाँ सार्वजनिक हों, और मूल कक्षा में निजी रूप से उन्हें सौंप दिया जाए। यह आपको अपने चश्मे में जादू मेटारुबी के बिना तरीकों का परीक्षण करने की अनुमति देगा, जबकि उन्हें अभी तक निजी रखा जा सकता है।

मुझे कई तरीके मिले हैं जो अच्छे और वैध कारणों से सुरक्षित या निजी हैं

वे वैध कारण क्या हैं? अन्य ओओपी भाषाएँ निजी तरीकों के बिना दूर हो सकती हैं (स्मालटाक के मन में आता है - जहां निजी तरीके केवल एक सम्मेलन के रूप में मौजूद हैं)।


हां, लेकिन ज्यादातर स्माल्टालकर्स को नहीं लगता था कि यह भाषा की अच्छी विशेषता है।
aenw

6

@ विलसर्जेंट की प्रतिक्रिया के समान, यहां मैंने एक 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

5

वर्णित वर्ग के लिए सभी संरक्षित और निजी विधि को सार्वजनिक करने के लिए, आप निम्नलिखित को अपने 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

3

आप कक्षा को "फिर से खोलना" कर सकते हैं और एक नया तरीका प्रदान कर सकते हैं जो निजी को दर्शाता है:

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

2

मैं शायद inst_eval () का उपयोग करने की ओर झुकाव होगा। इससे पहले कि मैं Inst_eval () के बारे में जानता, हालांकि, मैं अपनी इकाई परीक्षण फ़ाइल में एक व्युत्पन्न वर्ग बनाऊंगा। मैं तब सार्वजनिक होने के लिए निजी विधि निर्धारित करूंगा।

नीचे दिए गए उदाहरण में, Public_Sear: ISIQuery वर्ग में build_year_range तरीका निजी है। परीक्षण के प्रयोजनों के लिए एक नया वर्ग देने से मुझे सार्वजनिक होने की एक विधि निर्धारित करने की अनुमति मिलती है और इसलिए, सीधे परीक्षण योग्य है। इसी तरह, व्युत्पन्न वर्ग 'परिणाम' नामक एक उदाहरण चर को उजागर करता है जिसे पहले उजागर नहीं किया गया था।

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

मेरे यूनिट टेस्ट में मेरे पास एक टेस्ट केस है जो MockISIQuery क्लास को इंस्टेंट करता है और सीधे build_year_range () मेथड को टेस्ट करता है।


2

टेस्ट में :: यूनिट फ्रेमवर्क लिख सकता है,

MyClass.send(:public, :method_name)

यहाँ "method_name" निजी विधि है।

& इस पद्धति को कॉल करते समय लिख सकते हैं,

assert_equal expected, MyClass.instance.method_name(params)

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

संरक्षित / निजी तरीकों का उपयोग करने के लिए भेज का उपयोग 1.9 में टूट गया है, इसलिए अनुशंसित समाधान नहीं है।


1

ऊपर दिए गए शीर्ष उत्तर को सही करने के लिए: रूबी 1.9.1 में, यह ऑब्जेक्ट # भेजें जो सभी संदेश भेजता है, और ऑब्जेक्ट # public_send जो गोपनीयता का सम्मान करता है।


1
आपको उस उत्तर पर टिप्पणी जोड़नी चाहिए, दूसरे को सही करने के लिए नया उत्तर नहीं लिखना चाहिए।
जिशे

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.sendनिजी तरीकों के लिए send!1.9 द्वारा प्रतिस्थापित किया गया था , लेकिन बाद send!में फिर से हटा दिया गया था। तो obj.sendपूरी तरह से अच्छी तरह से काम करता है।


1

मुझे पता है कि मुझे पार्टी में देर हो रही है, लेकिन निजी तरीकों का परीक्षण न करें .... मैं ऐसा करने के लिए एक कारण के बारे में नहीं सोच सकता। सार्वजनिक रूप से सुलभ विधि उस निजी पद्धति का उपयोग कहीं न कहीं कर रही है, सार्वजनिक पद्धति और विभिन्न प्रकार के परिदृश्यों का परीक्षण करें जिससे उस निजी पद्धति का उपयोग किया जा सके। कुछ भीतर जाता है, कुछ बाहर आता है। निजी तरीकों का परीक्षण करना एक बड़ी संख्या में नहीं है, और यह बाद में आपके कोड को फिर से भरने के लिए बहुत कठिन बनाता है। वे एक कारण से निजी हैं।


14
अभी भी इस स्थिति को नहीं समझते हैं: हाँ, निजी कारण किसी कारण से निजी हैं, लेकिन नहीं, इस कारण का परीक्षण से कोई लेना-देना नहीं है।
सेबेस्टियन वोम मीर

काश मैं इसे और बढ़ा पाता। इस सूत्र में एकमात्र सही उत्तर है।
Psynix

यदि आपके पास वह दृष्टिकोण है तो यूनिट परीक्षणों से भी परेशान क्यों हैं? बस फीचर चश्मा लिखें: इनपुट अंदर जाता है, पेज निकलता है, बीच में सब कुछ सही होना चाहिए?
ओह्ह

1

ऐसा करने के लिए:

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

हे मैं के साथ इस कुछ मजेदार था: gist.github.com/amomchilov/ef1c84325fe6bb4ce01e0f0780837a82 नाम बदलकर Disrespectकरने के लिए PrivacyViolator(: पी) और बना disrespect_privacyविधि अस्थायी रूप से ब्लॉक के बंधन को संपादित करें, आवरण वस्तु को लक्ष्य वस्तु याद दिलाने के लिए इतनी के रूप में है, लेकिन केवल अवधि के लिए ब्लॉक के। इस तरह आपको एक ब्लॉक परम का उपयोग करने की आवश्यकता नहीं है, आप बस उसी नाम के साथ ऑब्जेक्ट को संदर्भित करना जारी रख सकते हैं।
अलेक्जेंडर -
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.