PHPUnit के साथ संरक्षित विधियों का परीक्षण करने के लिए सर्वोत्तम अभ्यास


287

मैंने चर्चा की कि क्या आप निजी पद्धति की जानकारीपूर्ण जाँच करते हैं।

मैंने फैसला किया है, कि कुछ वर्गों में, मैं संरक्षित विधियां करना चाहता हूं, लेकिन उनका परीक्षण करना चाहिए। इनमें से कुछ विधियां स्थिर और संक्षिप्त हैं। क्योंकि अधिकांश सार्वजनिक तरीके उनका उपयोग करते हैं, मैं शायद बाद में परीक्षणों को सुरक्षित रूप से हटा पाऊंगा। लेकिन एक टीडीडी दृष्टिकोण के साथ शुरू करने और डिबगिंग से बचने के लिए, मैं वास्तव में उनका परीक्षण करना चाहता हूं।

मैंने निम्नलिखित के बारे में सोचा:

  • एक उत्तर में सलाह दी गई विधि वस्तु इसके लिए ओवरकिल लगती है।
  • सार्वजनिक तरीकों से शुरू करें और जब उच्च स्तर के परीक्षणों द्वारा कोड कवरेज दिया जाता है, तो उन्हें संरक्षित करें और परीक्षणों को हटा दें।
  • संरक्षित तरीकों को सार्वजनिक करने वाले परीक्षण योग्य इंटरफ़ेस के साथ एक वर्ग इनहेरिट करें

सबसे अच्छा अभ्यास कौन सा है? क्या कुछ और है?

ऐसा लगता है, JUnit सार्वजनिक होने के लिए स्वचालित रूप से संरक्षित तरीकों को बदलता है, लेकिन मुझे इस पर गहराई से ध्यान नहीं दिया। PHP प्रतिबिंब के माध्यम से इसकी अनुमति नहीं देता है ।


दो सवाल: 1. आपको अपनी कार्यक्षमता का परीक्षण करने से क्यों परेशान होना चाहिए क्योंकि आपकी कक्षा उजागर नहीं होती है? 2. यदि आपको इसका परीक्षण करना चाहिए, तो यह निजी क्यों है?
nad2000

2
शायद वह परीक्षण करना चाहता है कि क्या एक निजी संपत्ति को सही ढंग से सेट किया जा रहा है और केवल सेट्टर फ़ंक्शन का उपयोग करके परीक्षण का एकमात्र तरीका निजी संपत्ति को सार्वजनिक करना और डेटा की जांच करना है
एंटोनियोकेस

4
और इसलिए यह चर्चा-शैली है और इस प्रकार रचनात्मक नहीं है। फिर से :)
mlvljr

72
आप इसे साइट के नियमों के खिलाफ कह सकते हैं, लेकिन इसे "रचनात्मक नहीं" कहना ... यह अपमानजनक है।
एंडी वी

1
@ विज़र, यह खुद को अपमानित कर रहा है;)
पचेरियर

जवाबों:


417

यदि आप PHP5nit के साथ PHP5 (> = 5.3.2) का उपयोग कर रहे हैं, तो आप अपने परीक्षणों को चलाने से पहले उन्हें सार्वजनिक करने के लिए प्रतिबिंब का उपयोग करके अपने निजी और संरक्षित तरीकों का परीक्षण कर सकते हैं:

protected static function getMethod($name) {
  $class = new ReflectionClass('MyClass');
  $method = $class->getMethod($name);
  $method->setAccessible(true);
  return $method;
}

public function testFoo() {
  $foo = self::getMethod('foo');
  $obj = new MyClass();
  $foo->invokeArgs($obj, array(...));
  ...
}

27
सेबस्टियन ब्लॉग के लिंक को उद्धृत करने के लिए: "तो: सिर्फ इसलिए कि संरक्षित और निजी विशेषताओं और विधियों का परीक्षण संभव नहीं है, इसका मतलब यह नहीं है कि यह" अच्छी बात है। "" - बस इसे ध्यान में रखने के लिए
एडोरियन

10
मैं वह चुनाव लडूंगा। यदि आपको काम करने के लिए अपने संरक्षित या निजी तरीकों की आवश्यकता नहीं है, तो उनका परीक्षण न करें।
इक़ेलमैन

10
बस स्पष्ट करने के लिए, आपको काम करने के लिए PHPUnit का उपयोग करने की आवश्यकता नहीं है। यह भी SimpleTest या जो भी साथ काम करेंगे। उत्तर के बारे में कुछ भी नहीं है जो PHPUnit पर निर्भर है।
इयान डुन

84
आपको संरक्षित / निजी सदस्यों का सीधे परीक्षण नहीं करना चाहिए। वे कक्षा के आंतरिक कार्यान्वयन से संबंधित हैं, और उन्हें परीक्षण के साथ जोड़ा नहीं जाना चाहिए। इससे रिफ़ेक्टिंग असंभव हो जाती है और अंततः आप परीक्षण नहीं करते हैं कि क्या परीक्षण करना है। आपको सार्वजनिक तरीकों का उपयोग करके अप्रत्यक्ष रूप से उनका परीक्षण करने की आवश्यकता है। यदि आपको यह मुश्किल लगता है, तो लगभग सुनिश्चित करें कि कक्षा की संरचना में कोई समस्या है और आपको इसे छोटी कक्षाओं में अलग करने की आवश्यकता है। ध्यान रखें कि आपकी परीक्षा के लिए आपकी कक्षा एक ब्लैक बॉक्स होनी चाहिए - आप किसी चीज़ में फेंकते हैं और आपको कुछ वापस मिल जाता है, और यह सब!
gphilip

24
@gphilip मेरे लिए, protectedविधि भी सार्वजनिक एपीआई का एक हिस्सा है क्योंकि कोई भी तृतीय पक्ष वर्ग इसे बढ़ा सकता है और बिना किसी जादू के इसका उपयोग कर सकता है। इसलिए मुझे लगता है कि केवल privateविधियों को सीधे परीक्षण नहीं किए जाने वाले तरीकों की श्रेणी में आते हैं। protectedऔर publicसीधे परीक्षण किया जाना चाहिए।
फ़िलिप हलक्सा

48

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

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

ध्यान दें कि आप हमेशा रचना के साथ विरासत को बदल सकते हैं। कोड का परीक्षण करते समय, आमतौर पर इस पैटर्न का उपयोग करने वाले कोड से निपटना बहुत आसान होता है, इसलिए आप उस विकल्प पर विचार कर सकते हैं।


2
आप सामान को सीधे सार्वजनिक रूप से लागू कर सकते हैं और माता-पिता को वापस कर सकते हैं :: सामान ()। मेरी प्रतिक्रिया देखें ऐसा लगता है कि मैं आज बहुत जल्दी चीजें पढ़ रहा हूं।
माइकल जॉनसन

आप सही हे; किसी संरक्षित विधि को सार्वजनिक रूप से बदलना मान्य है।
ट्रोल्सनॉक

इसलिए कोड मेरा तीसरा विकल्प बताता है और "ध्यान दें कि आप हमेशा रचना के साथ विरासत को बदल सकते हैं।" मेरे पहले विकल्प की दिशा में जाता है या refactoring.com/catalog/replaceInheritanceWithDelegation.html
GrGr

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

36
संरक्षित विधियां एक वर्ग के इंटरफेस का हिस्सा हैं , वे केवल कार्यान्वयन विवरण नहीं हैं। संरक्षित सदस्यों की पूरी बात इतनी है कि उप-वर्ग (अपने आप में उपयोगकर्ता) उन संरक्षित विधियों का उपयोग कक्षा के अंदर कर सकते हैं। जिन्हें स्पष्ट रूप से जांचने की आवश्यकता है।
बीटी

40

teastburn का सही तरीका है। यहां तक ​​कि सरल विधि को सीधे कॉल करना और उत्तर वापस करना है:

class PHPUnitUtil
{
  public static function callMethod($obj, $name, array $args) {
        $class = new \ReflectionClass($obj);
        $method = $class->getMethod($name);
        $method->setAccessible(true);
        return $method->invokeArgs($obj, $args);
    }
}

आप इसे बस अपने परीक्षणों में कह सकते हैं:

$returnVal = PHPUnitUtil::callMethod(
                $this->object,
                '_nameOfProtectedMethod', 
                array($arg1, $arg2)
             );

1
यह एक महान उदाहरण है, धन्यवाद। विधि को संरक्षित करने के बजाय सार्वजनिक होना चाहिए, क्या यह नहीं होना चाहिए?
वैलकम

अच्छी बात। मैं वास्तव में अपने बेस क्लास में इस पद्धति का उपयोग करता हूं कि मैं अपनी टेस्ट कक्षाओं का विस्तार करता हूं, जिस स्थिति में यह समझ में आता है। वर्ग का नाम हालांकि यहाँ गलत होगा।
रोबर्ट.नेगिंटन

मैं teastburn xD के आधार पर कोड के ठीक उसी टुकड़ा बना
Nebulosar

23

मैं uckelman के उत्तर में परिभाषित getMethod () को थोड़ी भिन्नता का प्रस्ताव देना चाहता हूं ।

यह संस्करण हार्ड-कोडित मूल्यों को हटाकर और उपयोग को थोड़ा सरल करके getMethod () में बदलाव करता है। मैं इसे अपने PHPUnitUtil वर्ग के रूप में नीचे दिए गए उदाहरण में या अपने PHPUnit_Framework_TestCase-extending वर्ग (या, मुझे लगता है, विश्व स्तर पर आपकी PHPUnitUtil फ़ाइल में जोड़ने की सलाह देता है)।

के बाद से MyClass वैसे भी त्वरित किया जा रहा है और ReflectionClass एक स्ट्रिंग या एक वस्तु ले जा सकते हैं ...

class PHPUnitUtil {
    /**
     * Get a private or protected method for testing/documentation purposes.
     * How to use for MyClass->foo():
     *      $cls = new MyClass();
     *      $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo');
     *      $foo->invoke($cls, $...);
     * @param object $obj The instantiated instance of your class
     * @param string $name The name of your private/protected method
     * @return ReflectionMethod The method you asked for
     */
    public static function getPrivateMethod($obj, $name) {
      $class = new ReflectionClass($obj);
      $method = $class->getMethod($name);
      $method->setAccessible(true);
      return $method;
    }
    // ... some other functions
}

मैंने एक उपनाम फ़ंक्शन getProtectedMethod () भी बनाया है जो स्पष्ट रूप से अपेक्षित है, लेकिन यह कि आप पर निर्भर है।

चीयर्स!


रिफ्लेक्शन क्लास एपीआई का उपयोग करने के लिए +1।
बिल ऑर्टेल

10

मुझे लगता है कि ट्रॉयल्सन करीब है। मैं इसके बजाय यह करूंगा:

class ClassToTest
{
   protected function testThisMethod()
   {
     // Implement stuff here
   }
}

फिर, कुछ इस तरह से लागू करें:

class TestClassToTest extends ClassToTest
{
  public function testThisMethod()
  {
    return parent::testThisMethod();
  }
}

आप तब TestClassToTest के विरुद्ध अपने परीक्षण चलाते हैं।

कोड को पार्स करके ऐसी एक्सटेंशन क्लासेस को स्वचालित रूप से जेनरेट करना संभव होना चाहिए। मुझे आश्चर्य नहीं होगा अगर PHPUnit पहले से ही इस तरह की व्यवस्था प्रदान करता है (हालांकि मैंने जाँच नहीं की है)।


हे ... ऐसा लगता है कि मैं कह रहा हूं, अपने तीसरे विकल्प का उपयोग करें :)
माइकल जॉनसन

2
हां, यह वास्तव में मेरा तीसरा विकल्प है। मुझे पूरा यकीन है, कि PHPUnit ऐसे तंत्र की पेशकश नहीं करता है।
जीआरआर

यह काम नहीं करेगा, आप एक सार्वजनिक फ़ंक्शन के साथ एक ही नाम के साथ एक संरक्षित फ़ंक्शन को ओवरराइड नहीं कर सकते।
कोएन।

मैं गलत हो सकता हूं, लेकिन मुझे नहीं लगता कि यह दृष्टिकोण काम कर सकता है। PHPUnit (जहां तक ​​मैंने कभी भी इसका उपयोग किया है) के लिए आवश्यक है कि आपका परीक्षण वर्ग एक अन्य वर्ग का विस्तार करे जो वास्तविक परीक्षण कार्यक्षमता प्रदान करता है। जब तक कि कोई ऐसा रास्ता नहीं है जिसके बारे में मुझे यकीन नहीं है कि मैं देख सकता हूं कि इस उत्तर का उपयोग कैसे किया जा सकता है। phpunit.de/manual/current/en/...
साइफर

1
FYI करें यह निजी संरक्षित तरीकों के लिए काम करता है , निजी लोगों के लिए नहीं
Sliq

5

मैं अपनी टोपी यहां रिंग में फेंकने जा रहा हूं:

मैंने सफलता की मिश्रित डिग्री के साथ __call हैक का उपयोग किया है। विज़िटर पैटर्न का उपयोग करने के लिए मैं जिस विकल्प के साथ आया था:

1: एक stdClass या कस्टम वर्ग उत्पन्न (प्रकार लागू करने के लिए)

2: आवश्यक विधि और तर्कों के साथ प्राइम

3: सुनिश्चित करें कि आपके SUT के पास एक स्वीकारकर्ता विधि है, जो आने वाले वर्ग में निर्दिष्ट तर्कों के साथ विधि को निष्पादित करेगी

4: इसे उस कक्षा में इंजेक्ट करें जिसे आप परीक्षण करना चाहते हैं

5: SUT विज़िटर में ऑपरेशन के परिणाम को इंजेक्ट करता है

6: विज़िटर की परिणाम विशेषता पर अपनी परीक्षण शर्तें लागू करें


1
एक दिलचस्प समाधान के लिए +1
जेएस

5

आप वास्तव में संरक्षित तरीकों तक पहुंचने के लिए जेनेरिक फैशन में __call () का उपयोग कर सकते हैं। इस वर्ग का परीक्षण करने में सक्षम होने के लिए

class Example {
    protected function getMessage() {
        return 'hello';
    }
}

आप exampleTest.php में एक उपवर्ग बनाएँ:

class ExampleExposed extends Example {
    public function __call($method, array $args = array()) {
        if (!method_exists($this, $method))
            throw new BadMethodCallException("method '$method' does not exist");
        return call_user_func_array(array($this, $method), $args);
    }
}

ध्यान दें कि __call () विधि किसी भी तरह से कक्षा का संदर्भ नहीं देती है इसलिए आप प्रत्येक कक्षा के लिए उपरोक्त तरीकों की प्रतिलिपि बना सकते हैं, जिनके साथ आप परीक्षण करना चाहते हैं और कक्षा की घोषणा को बदल सकते हैं। आप इस फ़ंक्शन को एक सामान्य आधार वर्ग में रखने में सक्षम हो सकते हैं, लेकिन मैंने इसे आज़माया नहीं है।

अब परीक्षण का मामला ही भिन्न होता है जहां आप परीक्षण की जाने वाली वस्तु का निर्माण करते हैं, उदाहरण के लिए उदाहरण में स्वैपिंग।

class ExampleTest extends PHPUnit_Framework_TestCase {
    function testGetMessage() {
        $fixture = new ExampleExposed();
        self::assertEquals('hello', $fixture->getMessage());
    }
}

मेरा मानना ​​है कि PHP 5.3 आपको सीधे तरीकों की पहुंच बदलने के लिए प्रतिबिंब का उपयोग करने की अनुमति देता है, लेकिन मुझे लगता है कि आपको प्रत्येक विधि के लिए व्यक्तिगत रूप से ऐसा करना होगा।


1
__Call () कार्यान्वयन महान काम करता है! मैंने वोट देने की कोशिश की, लेकिन मैंने इस पद्धति का परीक्षण करने के बाद तक अपना वोट रद्द कर दिया और अब एसओ में समय सीमा के कारण मुझे मतदान करने की अनुमति नहीं है।
एडम फ्रैंको

call_user_method_array()समारोह पीएचपी 4.1.0 के रूप में हटा दिया गया है ... का उपयोग call_user_func_array(array($this, $method), $args)करने के बजाय। ध्यान दें कि यदि आप PHP 5.3.2+ का उपयोग कर रहे हैं, तो आप संरक्षित / निजी तरीकों और विशेषताओं तक पहुंच प्राप्त करने के लिए
nuqqsa

@nuqsasa - धन्यवाद, मैंने अपना उत्तर अपडेट कर दिया है। मैंने तब से एक सामान्य Accessibleपैकेज लिखा है जो निजी / संरक्षित गुणों और कक्षाओं और वस्तुओं के तरीकों का परीक्षण करने की अनुमति देने के लिए प्रतिबिंब का उपयोग करता है।
डेविड हार्कस

यह कोड PHP 5.2.7 पर मेरे लिए काम नहीं करता है - __call विधि उन तरीकों के लिए नहीं आती है जो बेस क्लास को परिभाषित करता है। मैं इसे प्रलेखित नहीं कर सकता, लेकिन मुझे लगता है कि यह व्यवहार PHP 5.3 में बदल गया था (जहां मैंने पुष्टि की है कि यह काम करता है)।
रसेल डेविस

@ रसेल - __call()केवल तभी कॉल किया जाता है जब कॉलर के पास विधि तक पहुंच नहीं है। चूंकि कक्षा और उसके उपवर्गों में संरक्षित विधियों तक पहुंच है, इसलिए उन्हें कॉल से गुजरना नहीं होगा __call()। क्या आप अपना कोड पोस्ट कर सकते हैं जो एक नए प्रश्न में 5.2.7 में काम नहीं करता है? मैंने 5.2 में उपरोक्त का उपयोग किया और केवल 5.3.2 के साथ प्रतिबिंब का उपयोग करने के लिए चला गया।
डेविड हरकेंस

2

मेरा सुझाव है कि "हेनरिक पॉल" के वर्कअराउंड / विचार के लिए वर्कअराउंड :)

आप अपनी कक्षा के निजी तरीकों के नाम जानते हैं। उदाहरण के लिए वे _add (), _edit (), _delete () आदि जैसे हैं।

इसलिए जब आप इकाई परीक्षण के पहलू से यह परीक्षण करना चाहते हैं, बस लगाकर और / या कुछ प्रत्यय लगाना द्वारा निजी तरीकों फोन आम शब्द (उदाहरण के लिए _addPhpunit) ताकि __call () पद्धति को कॉल किया जाए (क्योंकि विधि _addPhpunit () नहीं है) स्वामी वर्ग का अस्तित्व), आपने बस __call () पद्धति में उपसर्ग / प्रत्यय शब्द / शब्द (फ़पुनिट) को हटाने के लिए और फिर उस कटौती की गई निजी विधि को वहां से हटाने के लिए आवश्यक कोड डाल दिया। यह जादू के तरीकों का एक और अच्छा उपयोग है।

कोशिश करके देखो।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.