简体   繁体   English

如何在php中单元测试curl调用

[英]how to unit test curl call in php

How would you go about unit testing a curl implementation? 您如何进行单元测试curl实现?

  public function get() {
    $ch = curl_init($this->request->getUrl());

    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $result = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);

    if (!strstr($type, 'application/json')) {
      throw new HttpResponseException('JSON response not found');
    }

    return new HttpResponse($code, $result);
  }

I need to test the content type returned so that it can throw an exception. 我需要测试返回的内容类型,以便它可以抛出异常。

As thomasrutter suggested, create a class to abstract the usage of the cURL functions. 正如thomasrutter建议的那样,创建一个类来抽象cURL函数的用法。

interface HttpRequest
{
    public function setOption($name, $value);
    public function execute();
    public function getInfo($name);
    public function close();
}

class CurlRequest implements HttpRequest
{
    private $handle = null;

    public function __construct($url) {
        $this->handle = curl_init($url);
    }

    public function setOption($name, $value) {
        curl_setopt($this->handle, $name, $value);
    }

    public function execute() {
        return curl_exec($this->handle);
    }

    public function getInfo($name) {
        return curl_getinfo($this->handle, $name);
    }

    public function close() {
        curl_close($this->handle);
    }
}

Now you can test using a mock of the HttpRequest interface without invoking any of the cURL functions. 现在,您可以使用HttpRequest接口的模拟进行测试,而无需调用任何cURL函数。

public function testGetThrowsWhenContentTypeIsNotJson() {
    $http = $this->getMock('HttpRequest');
    $http->expects($this->any())
         ->method('getInfo')
         ->will($this->returnValue('not JSON'));
    $this->setExpectedException('HttpResponseException');
    // create class under test using $http instead of a real CurlRequest
    $fixture = new ClassUnderTest($http);
    $fixture->get();
}

Edit Fixed simple PHP parse error. 编辑修复了简单的PHP解析错误。

Do not use curl directly but through a wrapper like PEAR's HTTP_Request2 . 不要直接使用curl,而是通过像PEAR的HTTP_Request2这样的包装器。 With it, you have the ability to exchange the curl driver with a mock driver - ideal for unit tests. 有了它,您就可以使用模拟驱动程序更换curl驱动程序 - 非常适合单元测试。

You might use a function mock library. 您可以使用函数模拟库。 I made one for you: php-mock-phpunit 我为你做了一个: php-mock-phpunit

namespace foo;

use phpmock\phpunit\PHPMock;

class BuiltinTest extends \PHPUnit_Framework_TestCase
{

    use PHPMock;

    public function testCurl()
    {
        $curl_exec = $this->getFunctionMock(__NAMESPACE__, "curl_exec");
        $curl_exec->expects($this->once())->willReturn("body");

        $ch = curl_init();
        $this->assertEquals("body", curl_exec($ch));
    }
}

I stumbled upon this question when I was trying to test a class using cURL myself. 当我试图用自己的cURL测试课时,我偶然发现了这个问题。 I took David Harkness's advice to heart and created an interface for cURL. 我把David Harkness的建议铭记于心,为cURL创建了一个界面。 However, the stub/mock functionality provided by PHPUnit was not sufficient in my case, so I added my own stub implementation of the interface and put it all on GitHub. 但是,PHPUnit提供的存根/模拟功能在我的情况下是不够的,所以我添加了自己的接口的存根实现并将其全部放在GitHub上。 And because this question shows up rather early on Google when searching this issue, I thought I would post it here, so the others might be able to save the effort. 而且因为这个问题在搜索这个问题时很早就出现在谷歌上,我想我会在这里发布,所以其他人可能会省力。

Here it is. 这里是。

The repository's wiki contains a rather detailed documentation of the stub's capabilities, but here they are in short. 存储库的wiki包含了存根功能的相当详细的文档,但在这里它们很简单。

The interface is a 1:1 mapping of PHP's cURL functions, so as to make it very easy to start using the interface (simply hand your ClassUnderTest an instance implementing SAI_CurlInterface and then call all cURL functions as before, but as methods on that instance). 该接口是PHP的cURL函数的1:1映射,以便使开始使用该接口变得非常容易(只需将您的ClassUnderTest实现为实现SAI_CurlInterface的实例,然后像以前一样调用所有cURL函数,但作为该实例上的方法) 。 The class SAI_Curl implements this interface by simply delegating to cURL. SAI_Curl类通过简单地委托给cURL来实现这个接口。 Now if you want to test the ClassUnderTest you can give it an instance of SAI_CurlStub . 现在,如果要测试ClassUnderTest ,可以为其提供SAI_CurlStub的实例。

The stub mainly alleviates the problem that PHPUnit's mocks and stubs cannot returned dummy data depending on former function calls (but this is how cURL actually works - you set up your options and the response, error code and cURL-info depend on those options). 存根主要解决了PHPUnit的模拟和存根无法根据以前的函数调用返回虚拟数据的问题(但这是cURL实际工作的方式 - 您设置选项,响应,错误代码和cURL-info取决于这些选项)。 So here is a short example, showing those capabilities for responses (for error codes and cURL-info, see the wiki). 所以这是一个简短的例子,显示了响应的能力(对于错误代码和cURL-info,请参阅wiki)。

public function testGetData()
{
    $curl = new SAI_CurlStub();

    // Set up the CurlStub
    $defaultOptions = array(
        CURLOPT_URL => 'http://www.myserver.com'
    );
    $chromeOptions = array(
        CURLOPT_URL => 'http://www.myserver.com',
        CURLOPT_USERAGENT => 'Chrome/22.0.1207.1'
    );
    $safariOptions = array(
        CURLOPT_URL => 'http://www.myserver.com',
        CURLOPT_USERAGENT => 'Safari/537.1'
    );

    $curl->setResponse('fallback response');
    $curl->setResponse('default response from myserver.com'
                       $defaultOptions);
    $curl->setResponse('response for Chrome from myserver.com',
                       $chromeOptions);
    $curl->setResponse('response for Safari from myserver.com',
                       $safariOptions);

    $cut = new ClassUnderTest($curl);

    // Insert assertions to check whether $cut handles the
    // different responses correctly
    ...
}

You can make your response dependent on any combination of any cURL-options. 您可以根据任何cURL选项的任意组合进行响应。 Of course, you can take this even further. 当然,你可以更进一步。 Say for example, your ClassUnderTest takes some XML data from a server and parses it (well, you should have two separate classes for those tasks, but let's assume this for our example), and you want to test that behavior. 比如说,你的ClassUnderTest从服务器获取一些XML数据并对其进行解析(好吧,你应该为这些任务设置两个独立的类,但是我们假设这是为了我们的例子),并且你想测试那个行为。 You could download the XML response manually, and have your test read the data from the file and stuff it into the response. 您可以手动下载XML响应,让测试从文件中读取数据并将其填入响应中。 Then you know exactly what data is there, and can check whether it's parsed correctly. 然后你确切地知道那里有什么数据,并且可以检查它是否被正确解析。 Alternatively, you could implement the SAI_CurlInterface loading all responses from your file system right away, but the existing implementation is definitely a point to start. 或者,您可以实现SAI_CurlInterface加载文件系统中的所有响应,但现有的实现绝对是一个重要的开始点。

At the time that II am writing this answer, @SAI_CurlStub@ does not support cURL multi-lib features yet, but I plan to implement this, too, in the future. 在我写这个答案的时候,@ SAI_CurlStub @还不支持cURL multi-lib功能,但我计划将来也实现这个功能。

I hope this stub is of help to anyone who wants to unit test cURL-dependent classes. 我希望这个存根对任何想要单元测试cURL依赖类的人都有帮助。 Feel free to check out and use the classes, or contribute, of course - it's on GitHub after all :). 随意检查并使用课程,或者贡献,当然 - 毕竟它是在GitHub上:)。 Also, I am open to any constructive criticism regarding implementation and usage of interface and stub. 此外,我对任何有关接口和存根的实现和使用的建设性批评持开放态度。

One approach to this involves replacing the interface you are using (in this case, the curl_ functions) with dummy versions of themselves which return certain values. 一种方法是将您正在使用的接口(在本例中为curl_函数)替换为自身的虚拟版本,这些版本返回某些值。 If you were using an object-oriented library this would be easier because you could just substitute an dummy object which has the same method names (and indeed, frameworks like simpletest can set up dummy object methods easily). 如果您使用面向对象的库,这将更容易,因为您可以替换具有相同方法名称的虚拟对象(实际上,像simpletest这样的框架可以轻松地设置虚拟对象方法)。 Otherwise, perhaps there is some other sorcery you can use to override built-in functions with dummies. 否则,也许还有一些其他巫术可以用来覆盖傻瓜的内置函数。 This extension includes override_function() which looks like what you'd need, though that would add another dependency. 这个扩展包括override_function() ,它看起来像你需要的,虽然这将添加另一个依赖项。

If you want to test this without replacing the curl_ functions with dummy versions, it looks like you will need to set up a dummy server that will return a certain result, so that you can test the way your PHP, and its curl extension, handles that result. 如果你想在不用虚拟版本替换curl_函数的情况下测试它,看起来你需要设置一个将返回某个结果的虚拟服务器,以便你可以测试你的PHP及其curl扩展处理方式结果。 To fully test it, you'd need to access this over HTTP rather than, say, a local file, because your PHP depends on having an HTTP response code, etc. So your tests will need a functioning HTTP server. 要完全测试它,您需要通过HTTP而不是本地文件来访问它,因为您的PHP依赖于具有HTTP响应代码等。因此您的测试将需要一个正常运行的HTTP服务器。

Incidentally, PHP 5.4 will actually include its own web server which would come in handy for this purpose. 顺便说一句, PHP 5.4实际上将包含它自己的Web服务器 ,它将为此目的派上用场。 Otherwise, you could put a test script on a known server which you control, or distribute a simple server config with your tests. 否则,您可以将测试脚本放在您控制的已知服务器上,或者使用测试分发简单的服务器配置。

If you were to actually use the live server for your testing, this would become less of a unit test and more of an integration test, because you be testing both your PHP and the server, and the integration between the two. 如果您实际使用实时服务器进行测试,那么这将不再是单元测试,而是更多的集成测试,因为您要测试PHP和服务器,以及两者之间的集成。 You would also miss out on being able to test on demand how your code handles certain failures. 您也会错过能够按需测试代码如何处理某些故障。

在单元测试中,请request->getUrl()返回您知道将引发异常的本地文件的URI。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM