PHPUnit: नकली वस्तुएँ

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

उपयोग किए गए उदाहरण के रूप में, निम्न वर्ग विवरण लें:
class MyClass { protected function showWord($word) { /*       */ } protected function getTemperature() { /*     */ } public getWord($temparature) { $temperature = (int)$temparature; if ($temperature < 15) { return 'cold'; } if ($temperature > 25) { return 'hot'; } return 'warm'; } public function process() { $temperature = $this->getTemperature(); $word = $this->getWord($temperature); $this->showWord($word); } } 

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

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

मॉक ऑब्जेक्ट प्राप्त करना


मॉक ऑब्जेक्ट का एक उदाहरण प्राप्त करने के लिए, getMock () विधि का उपयोग करें:
 class MyClassTest extends PHPUnit_Framework_TestCase { public function test_process() { $mock = $this->getMock('MyClass'); // ,   $mock    MyClass $this->assertInstanceOf('MyClass', $mock); } } 

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

GetMock कॉल विकल्प

 public function getMock( $originalClassName, //   ,     Mock  $methods = array(), //           array $arguments = array(), // ,    $mockClassName = '', //    Mock  $callOriginalConstructor = true, //   __construct() $callOriginalClone = true, //   __clone() $callAutoload = true //   __autoload() ); 

पास करने के लिए दूसरे तर्क के रूप में बिल्डर को पास करने के लिए getMock () पास करने से मॉक ऑब्जेक्ट को बिना किसी प्रतिस्थापन के वापस कर दिया जाएगा।

getMockBuilder

जो लोग श्रृंखला शैली में लिखना पसंद करते हैं, उनके लिए PHPUnit एक उपयुक्त निर्माता प्रदान करता है:
 $mock = $this->getMockBuilder('MyClass') ->setMethods(null) ->setConstructorArgs(array()) ->setMockClassName('') //   ,   Mock  "" ->disableOriginalConstructor() ->disableOriginalClone() ->disableAutoload() ->getMock(); 

श्रृंखला हमेशा getMockBuilder () विधि से शुरू होनी चाहिए और getMock () विधि के साथ डाउनलोड की जानी चाहिए - ये श्रृंखला में एकमात्र लिंक हैं जो आवश्यक हैं।

नकली वस्तुओं को प्राप्त करने के अतिरिक्त तरीके


यह सब अद्भुत है - आप कहते हैं, लेकिन आगे क्या? जवाब में, मैं कहूंगा कि हम अभी सबसे दिलचस्प आए।

एक विधि कॉल की प्रतीक्षा की जा रही है


PHPUnit हमें प्रतिस्थापित विधियों के लिए कॉल की संख्या और क्रम को नियंत्रित करने की अनुमति देता है। ऐसा करने के लिए, विधि () का उपयोग करके वांछित विधि द्वारा पीछा किए गए एक्सपेक्ट्स () निर्माण का उपयोग करें। एक उदाहरण के रूप में, आइए लेख की शुरुआत में कक्षा को देखें और इसके लिए निम्नलिखित परीक्षा लिखें:
 public function test_process() { $mock = $this->getMock('MyClass', array('getTemperature', 'getWord', 'showWord')); $mock->expects($this->once())->method('getTemperature'); $mock->expects($this->once())->method('showWord'); $mock->expects($this->once())->method('getWord'); $mock->process(); } 

इस परीक्षण का परिणाम तब सफल होगा, जब प्रक्रिया () विधि को बुलाते समय, तीन सूचीबद्ध विधियों के लिए एक एकल कॉल होता है: गेटटेंस (), गेटवॉर्ड (), शोवर्ल्ड ()। ध्यान दें कि परीक्षण में, getWord () को कॉल को showWord () को कॉल करने के बाद जांचा जाता है, हालांकि परीक्षण विधि में विपरीत सत्य है। यह सही है, यहां कोई त्रुटि नहीं है। PHPUnit में विधि कॉल के आदेश को नियंत्रित करने के लिए, एक और निर्माण का उपयोग किया जाता है - पर ()। आइए हमारे परीक्षण कोड को थोड़ा ठीक करें ताकि PHPUnit उसी समय विधि कॉल के अनुक्रम की जाँच करें:
 public function test_process() { $mock = $this->getMock('MyClass', array('getTemperature', 'getWord', 'showWord')); $mock->expects($this->at(0))->method('getTemperature'); $mock->expects($this->at(2))->method('showWord'); $mock->expects($this->at(1))->method('getWord'); $mock->process(); } 

PHPUnit में कॉल अपेक्षाओं के परीक्षण के लिए एक बार () और पर () उल्लेख के अलावा, निम्नलिखित निर्माण भी हैं: कोई भी (), कभी नहीं (), कम से कम () और बिल्कुल ($ गिनती)। उनके नाम खुद बोलते हैं।

परिणाम ओवरराइड करें


अब तक, मॉक ऑब्जेक्ट्स की सबसे उपयोगी विशेषता स्पूफेड विधियों द्वारा लौटाए गए परिणाम का अनुकरण करने की क्षमता है। हम फिर से अपनी कक्षा की प्रक्रिया () पद्धति की ओर मुड़ते हैं। हम वहां तापमान सेंसर के लिए एक अपील देखते हैं - गेटटैन्स ()। लेकिन हमें यह भी याद है कि वास्तव में हमारे पास सेंसर नहीं है। हालांकि अगर हमारे पास यह था, तो भी हम सभी संभावित स्थितियों का परीक्षण करने के लिए इसे 15 डिग्री से नीचे या 25 से ऊपर गर्म नहीं करेंगे। जैसा कि आप अनुमान लगा सकते हैं, इस मामले में मॉक ऑब्जेक्ट हमारी सहायता के लिए आते हैं। हम अपनी इच्छानुसार कोई भी परिणाम लौटा सकते हैं। यहाँ एक उदाहरण है:
 /** * @dataProvider provider_process */ public function test_process($temperature) { $mock = $this->getMock('MyClass', array('getTemperature', 'getWord', 'showWord')); //  getTemperature()   $temperature $mock->expects($this->once())->method('getTemperature')->will($this->returnValue($temperature)); $mock->process(); } public static function provider_process() { return array( 'cold' => array(10), 'warm' => array(20), 'hot' => array(30), ); } 

जाहिर है, इस परीक्षा में उन सभी संभावित मूल्यों को शामिल किया गया है, जिन्हें हमारा परीक्षण वर्ग संभाल सकता है। PHPUnit वसीयत के साथ उपयोग के लिए निम्नलिखित निर्माण प्रदान करता है ():

मान्य तर्क


परीक्षण के लिए मॉक ऑब्जेक्ट्स की एक अन्य उपयोगी विशेषता निर्माण () निर्माण के साथ एक स्पूफ़ विधि का उपयोग करते समय निर्दिष्ट तर्कों की जाँच कर रही है:
 public function test_with_and_will_usage() { $mock = $this->getMock('MyClass', array('getWord')); $mock->expects($this->once()) ->method('getWord') ->with($this->greaterThan(25)) ->will($this->returnValue('hot')); $this->assertEquals('hot', $mock->getWord(30)); } 

जैसा कि तर्क () सभी निर्माणों को मुखर () जांच के रूप में ले सकते हैं, इसलिए यहां मैं एक विस्तृत विवरण के बिना केवल संभावित निर्माणों की एक सूची प्रदान करूंगा:

इन सभी निर्माणों को तार्किक कंस्ट्रक्ट्स लॉजिकलएंड (), लॉजिकलऑर (), लॉजिकलनॉट () और लॉजिकलऑनर () का उपयोग करके जोड़ा जा सकता है:
 $mock->expects($this->once()) ->method('getWord') ->with($this->logicalAnd($this->greaterThanOrEqual(15), $this->lessThanOrEqual(25))) ->will($this->returnValue('warm')); 

अब जब हम PHPUnit में नकली वस्तुओं की क्षमताओं से पूरी तरह परिचित हैं, तो हम अपनी कक्षा का अंतिम परीक्षण कर सकते हैं:
 /** * @dataProvider provider_process */ public function test_process($temperature, $expected_word) { //  Mock ,  getWord()  process()      $mock = $this->getMock('MyClass', array('getTemperature', 'showWord')); //  getTemperature()    $temperature $mock->expects($this->once())->method('getTemperature')->will($this->returnValue($temperature)); // ,   showWord()    $expected_word $mock->expects($this->once())->method('showWord')->with($this->equalTo($expected_word)); //  $mock->process(); } public static function provider_process() { return array( 'cold' => array(10, 'cold'), 'warm' => array(20, 'warm'), 'hot' => array(30, 'hot'), ); } 

UPD: VolCh ने ठीक ही टिप्पणी की है कि ऊपर प्रस्तुत किया गया जैसे लेखन परीक्षण एंटीपैटर्न है। इसलिए, उपरोक्त उदाहरण को केवल PHPUnit में मॉक ऑब्जेक्ट्स की क्षमताओं के साथ खुद को परिचित करने के लिए माना जाना चाहिए।

स्थैतिक विधि प्रतिस्थापन


PHPUnit संस्करण 3.5 के साथ शुरू करना, स्थैतिक निर्माण staticExpects () का उपयोग करके स्थिर तरीकों को बदलना संभव है:
 $class = $this->getMockClass('SomeClass'); //    PHP  5.3   //       call_user_func_array() $class::staticExpects($this->once())->method('someStaticMethod'); $class::someStaticMethod(); 

इस नवाचार के लिए व्यावहारिक अनुप्रयोग होने के लिए, यह आवश्यक है कि परीक्षण किए गए वर्ग के अंदर बदली हुई स्थिर विधि का कॉल निम्न में से किसी एक में हो:

स्वयं की सीमाओं के कारण , क्लास के अंदर इस तरह से कहे जाने वाले स्थिर तरीकों का प्रतिस्थापन काम नहीं करेगा:
 self::staticMethod(); 

निष्कर्ष


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

Source: https://habr.com/ru/post/In140217/


All Articles