2015-12-10 13 views
5

में कर्ल का उपयोग करने वाली सेवा का परीक्षण करने की आवश्यकता है मैंने अपने लैरावेल 5.1 एपीआई के लिए एक सेवा बनाई जो यूट्यूब की खोज करता है। मैं इसके लिए एक परीक्षण लिखने की कोशिश कर रहा हूं लेकिन कार्यक्षमता का नकल करने के तरीके को समझने में मुझे परेशानी हो रही है। नीचे सेवा है।लार्वेल 5.1

class Youtube 
{ 
/** 
* Youtube API Key 
* 
* @var string 
*/ 
protected $apiKey; 

/** 
* Youtube constructor. 
* 
* @param $apiKey 
*/ 
public function __construct($apiKey) 
{ 
    $this->apiKey = $apiKey; 
} 

/** 
* Perform YouTube video search. 
* 
* @param $channel 
* @param $query 
* @return mixed 
*/ 
public function searchYoutube($channel, $query) 
{ 
    $url = 'https://www.googleapis.com/youtube/v3/search?order=date' . 
     '&part=snippet' . 
     '&channelId=' . urlencode($channel) . 
     '&type=video' . 
     '&maxResults=25' . 
     '&key=' . urlencode($this->apiKey) . 
     '&q=' . urlencode($query); 
    $ch = curl_init(); 
    curl_setopt($ch, CURLOPT_URL, $url); 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
    $result = curl_exec($ch); 
    curl_close($ch); 

    $result = json_decode($result, true); 

    if (is_array($result) && count($result)) { 
     return $this->extractVideo($result); 
    } 
    return $result; 
} 

/** 
* Extract the information we want from the YouTube search resutls. 
* @param $params 
* @return array 
*/ 
protected function extractVideo($params) 
{ 
    /* 
    // If successful, YouTube search returns a response body with the following structure: 
    // 
    //{ 
    // "kind": "youtube#searchListResponse", 
    // "etag": etag, 
    // "nextPageToken": string, 
    // "prevPageToken": string, 
    // "pageInfo": { 
    // "totalResults": integer, 
    // "resultsPerPage": integer 
    // }, 
    // "items": [ 
    // { 
    //  "kind": "youtube#searchResult", 
    //  "etag": etag, 
    //  "id": { 
    //   "kind": string, 
    //   "videoId": string, 
    //   "channelId": string, 
    //   "playlistId": string 
    //  }, 
    //  "snippet": { 
    //   "publishedAt": datetime, 
    //   "channelId": string, 
    //   "title": string, 
    //   "description": string, 
    //   "thumbnails": { 
    //    (key): { 
    //     "url": string, 
    //     "width": unsigned integer, 
    //     "height": unsigned integer 
    //    } 
    //   }, 
    //  "channelTitle": string, 
    //  "liveBroadcastContent": string 
    //  } 
    // ] 
    //} 
    */ 
    $results = []; 
    $items = $params['items']; 

    foreach ($items as $item) { 

     $videoId = $items['id']['videoId']; 
     $title = $items['snippet']['title']; 
     $description = $items['snippet']['description']; 
     $thumbnail = $items['snippet']['thumbnails']['default']['url']; 

     $results[] = [ 
      'videoId' => $videoId, 
      'title' => $title, 
      'description' => $description, 
      'thumbnail' => $thumbnail 
     ]; 
    } 

    // Return result from YouTube API 
    return ['items' => $results]; 
} 
} 

मैंने इस सेवा को नियंत्रक से कार्यक्षमता को सारणी बनाने के लिए बनाया है। मैंने नियंत्रक का परीक्षण करने के लिए मॉकरी का इस्तेमाल किया। अब मुझे यह समझने की जरूरत है कि उपर्युक्त सेवा का परीक्षण कैसे करें। किसी भी मदद की सराहना की है।

उत्तर

3

कहने की आवश्यकता है कि आपकी कक्षा को पृथक इकाई परीक्षण के लिए डिज़ाइन नहीं किया गया है क्योंकि हार्डकोडेड curl_* विधियों के कारण।

1) curl_* कार्यों निकालें एक और वर्ग के लिए कॉल करता है और एक पैरामीटर

class CurlCaller { 

    public function call($url) { 
     $ch = curl_init(); 
     curl_setopt($ch, CURLOPT_URL, $url); 
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
     $result = curl_exec($ch); 
     curl_close($ch); 
     return $result; 
    } 

} 

class Youtube 
{ 
    public function __construct($apiKey, CurlCaller $caller) 
    { 
     $this->apiKey = $apiKey; 
     $this->caller = $caller; 
    } 
} 

अब आप आसानी से नकली CurlCaller वर्ग के रूप में उस वर्ग पारित: के लिए बेहतर आप कम से कम 2 विकल्प हैं बनाते हैं। नेटवर्क तैयार करने वाले बहुत सारे तैयार समाधान हैं। उदाहरण के लिए, Guzzle महान

2) एक और विकल्प curl_* सुरक्षित विधि पर कॉल निकालने और उस विधि को नकली करने का है।

// Firstly change your class: 
class Youtube 
{ 
    // ... 

    public function searchYoutube($channel, $query) 
    { 
     $url = 'https://www.googleapis.com/youtube/v3/search?order=date' . 
      '&part=snippet' . 
      '&channelId=' . urlencode($channel) . 
      '&type=video' . 
      '&maxResults=25' . 
      '&key=' . urlencode($this->apiKey) . 
      '&q=' . urlencode($query); 
     $result = $this->callUrl($url); 

     $result = json_decode($result, true); 

     if (is_array($result) && count($result)) { 
      return $this->extractVideo($result); 
     } 
     return $result; 
    } 

    // This method will be overriden in test. 
    protected function callUrl($url) 
    { 
     $ch = curl_init(); 
     curl_setopt($ch, CURLOPT_URL, $url); 
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
     $result = curl_exec($ch); 
     curl_close($ch); 

     return $result; 
    } 
} 

अब आप विधि callUrl नकली कर सकते हैं: यहाँ एक काम कर उदाहरण है। लेकिन सबसे पहले, fixtures/youtube-response-stub.json फ़ाइल पर अपेक्षित एपीआई प्रतिक्रिया दें।

class YoutubeTest extends PHPUnit_Framework_TestCase 
{ 
    public function testYoutube() 
    { 
     $apiKey = 'StubApiKey'; 

     // Here we create instance of Youtube class and tell phpunit that we want to override method 'callUrl' 
     $youtube = $this->getMockBuilder(Youtube::class) 
      ->setMethods(['callUrl']) 
      ->setConstructorArgs([$apiKey]) 
      ->getMock(); 

     // This is what we expect from youtube api but get from file 
     $fakeResponse = $this->getResponseStub(); 

     // Here we tell phpunit how to override method and our expectations about calling it 
     $youtube->expects($this->once()) 
      ->method('callUrl') 
      ->willReturn($fakeResponse); 

     // Get results 
     $list = $youtube->searchYoutube('UCSZ3kvee8aHyGkMtShH6lmw', 'php'); 

     $expected = ['items' => [[ 
      'videoId' => 'video-id-stub', 
      'title' => 'title-stub', 
      'description' => 'description-stub', 
      'thumbnail' => 'https://i.ytimg.com/vi/stub/thimbnail-stub.jpg', 
     ]]]; 

     // Finally assert result with what we expect 
     $this->assertEquals($expected, $list); 
    } 

    public function getResponseStub() 
    { 
     $response = file_get_contents(__DIR__ . '/fixtures/youtube-response-stub.json'); 
     return $response; 
    } 
} 

भागो परीक्षण और ... OMG विफलता !! 1 आप extractVideo विधि में लेखन त्रुटि, $item बजाय $items होना चाहिए। चलिए इसे

$videoId = $item['id']['videoId']; 
$title = $item['snippet']['title']; 
$description = $item['snippet']['description']; 
$thumbnail = $item['snippet']['thumbnails']['default']['url']; 

ठीक है, अब यह पास हो गया है।


यदि आप यूट्यूब एपीआई को कॉल के साथ अपनी कक्षा का परीक्षण करना चाहते हैं तो आपको सामान्य यूट्यूब कक्षा बनाने की जरूरत है।


BTW, वहाँ php-youtube-api lib, जो laravel 4 और laravel 5 के लिए प्रदाताओं है, यह भी यह परीक्षण

+0

बहुत बहुत धन्यवाद! – WebDev84

0

है कोड जहां कर्ल कॉल बना रहे हैं बदल रहा है एक विकल्प नहीं है, यह कर सकते हैं अभी भी किया जा सकता है, लेकिन यह सुंदर नहीं है।

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

  1. परीक्षण के भीतर, का उपयोग कर कर्ल कॉल के डोमेन के लिए जिम्मेदार पर्यावरण चर के मूल्य में परिवर्तन: putenv("SOME_BASE_URI=".config('app.url')."/curltest/")

phpunit.xml के बाद से आमतौर पर डिफ़ॉल्ट CACHE_DRIVERarray है, जो नहीं है सेट स्थायी, आपको इसे अपने परीक्षण में file पर बदलने के लिए इसे रखना होगा।

config(['cache.default' => 'file']); 
  1. अपने tests फ़ोल्डर के भीतर एक नया वर्ग है कि किसी भी प्रतिक्रिया लौटाएँ जब अनुरोध मानदंडों के एक विन्यास सेट को पूरा करती है के लिए जिम्मेदार होगा बनाएँ:

    उपयोग रोशन \ http \ अनुरोध;

    वर्ग ResponseFactory {

    public function getResponse(Request $request) 
    { 
        $request = [ 
         'method' => $request->method(), 
         'url' => parse_url($request->fullUrl()), 
         'parameters' => $request->route()->parameters(), 
         'input' => $request->all(), 
         'files' => $request->files 
        ]; 
    
        $responses = app('cache')->pull('test-response', null); 
    
        $response = collect($responses)->filter(function (array $response) use ($request) { 
         $passes = true; 
         $response = array_dot($response); 
         $request = array_dot($request); 
         foreach ($response as $part => $rule) { 
          if ($part == 'response') { 
           continue; 
          } 
          $passes &= is_callable($rule) ? $rule($request[$part]) : ($request[$part] == $rule); 
         } 
         return $passes; 
        })->pluck('response')->first() ?: $request; 
    
        if (is_callable($response)) { 
         $response = $response($request); 
        } 
    
        return response($response); 
    } 
    
    /** 
    * This uses permanent cache so it can persist between the instance of this app from which the test is being 
    * executed, to the instance being accessed by a CURL call 
    * 
    * @param array $responses 
    */ 
    public function setResponse(array $responses) 
    { 
        app('cache')->forever('test-response', $responses); 
    } 
    

    }

के बाद से इस tests फ़ोल्डर में है और App नाम स्थान में नहीं, अपने composer.json फ़ाइल के auto-load.classmap भाग में जोड़ने के लिए सुनिश्चित हो, और कमांड लाइन पर composer dumpautoload;composer install चलाएं। इसके अलावा, इस एक कस्टम सहायक समारोह उपयोग कर रहा है:

if (!function_exists('parse_url')) { 
    /** 
    * @param $url 
    * @return array 
    */ 
    function parse_url($url) 
    { 
     $parts = parse_url($url); 
     if (array_key_exists('query', $parts)) { 
      $query = []; 
      parse_str(urldecode($parts['query']), $query); 
      $parts['query'] = $query; 
     } 
     return $parts; 
    } 
} 
  1. अपने मार्गों के लिए कुछ परीक्षण केवल-अंतिम बिंदु जोड़ें। (खेद है, आपकी परीक्षण भीतर $this->app->make(Router::class)->match($method, $endpoint, $closure); रखकर काम करेंगे नहीं, अब तक के रूप में मैं बता सकते हैं।) Route::post('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); Route::get('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); Route::put('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); Route::patch('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); Route::delete('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); तुम भी एक if ब्लॉक में इस लपेट कर सकते हैं अगर आप चाहते हैं, यह सुनिश्चित करने config('app.debug') == true पहले बना देता है।

  2. एक विशिष्ट response मान को इंगित करने के लिए अनुमानित अंत बिंदु को प्रतिबिंबित करने के लिए प्रतिक्रियाओं की सामग्री को कॉन्फ़िगर करें। अपने परीक्षण के अंदर ऐसा कुछ रखें। app(ResponseFactory::class)->setResponse([[ 'url.path' => "/curltest/$curlTargetEndpont", 'response' => 'success' ]]);

संबंधित मुद्दे