【问题标题】:how to unit test curl call in php如何在php中对curl调用进行单元测试
【发布时间】:2011-10-27 03:52:08
【问题描述】:

您将如何对 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);
  }

我需要测试返回的内容类型,以便它可以抛出异常。

【问题讨论】:

    标签: php curl phpunit


    【解决方案1】:

    按照 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);
        }
    }
    

    现在您可以使用 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();
    }
    

    编辑修正简单的 PHP 解析错误。

    【讨论】:

    • 是的,非常好的解决方案。启发我为 cURL 构建一个成熟的存根类。任何 PHPUnit 的模拟和存根功能不足的人,请在下面查看我的答案:)。
    • 如果我不想将 CurlRequest 对象传递给构造函数,并直接从构造函数中创建它以简化构造函数调用,该怎么办?那你会怎么嘲笑它呢?
    • @Dbugger - 在构造函数调用的单独方法中实例化 CurlRequest。在您的测试用例中,延迟调用构造函数,直到您模拟了返回模拟请求的方法。有关示例,请参见 stackoverflow.com/a/5548229/285873
    • @Dbugger - 不,你不能用 PHPUnit 模拟私有方法。您必须对其进行保护。
    • @David Harkness 我试图使用此代码,因此为接口和类创建了文件,但是当我在测试用例文件中编写代码时,如上所示,收到类似“1)TopupHandlerTest: :testGetThrowsWhenLegacyApiIsIsBroken 尝试配置无法配置的方法“getInfo”,因为它不存在、尚未指定、是最终的还是静态的“我在做什么错误?你能帮忙吗
    【解决方案2】:

    您可以使用函数模拟库。我给你做了一个: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));
        }
    }
    

    【讨论】:

    • 很少需要直接在测试套件中测试内置的 PHP。您能否提供一个示例来覆盖由测试用例调用的应用程序类调用的 curl_init()。
    【解决方案3】:

    不要直接使用 curl,而是通过像 PEAR 的 HTTP_Request2 这样的包装器。有了它,您就可以将 curl 驱动程序与模拟驱动程序交换 - 非常适合单元测试。

    【讨论】:

      【解决方案4】:

      当我自己尝试使用 cURL 测试一个类时,我偶然发现了这个问题。我将 David Harkness 的建议铭记于心,并为 cURL 创建了一个界面。但是,PHPUnit 提供的 stub/mock 功能在我的情况下是不够的,所以我添加了自己的接口实现,并将其全部放在 GitHub 上。而且因为这个问题在谷歌搜索这个问题时很早就出现了,所以我想我会在这里发布它,这样其他人也许可以节省工作量。

      Here it is.

      repository's wiki 包含有关存根功能的相当详细的文档,但在这里它们很简短。

      接口是PHP的cURL函数的1:1映射,这样可以很容易上手使用接口(只需将你的ClassUnderTest交给一个实现SAI_CurlInterface的实例,然后像以前一样调用所有cURL函数,但作为该实例的方法)。 SAI_Curl 类通过简单地委托给 cURL 来实现这个接口。现在如果你想测试ClassUnderTest,你可以给它一个SAI_CurlStub的实例。

      stub 主要缓解 PHPUnit 的 mocks 和 stubs 不能根据以前的函数调用返回虚拟数据的问题(但这就是 cURL 的实际工作方式 - 你设置你的选项,响应、错误代码和 cURL 信息取决于那些选项)。因此,这里有一个简短的示例,展示了响应的这些功能(有关错误代码和 cURL 信息,请参阅 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
          ...
      }
      

      您可以根据任何 cURL 选项的任意组合做出响应。当然,你可以更进一步。例如,您的ClassUnderTest 从服务器获取一些 XML 数据并对其进行解析(好吧,您应该为这些任务有两个单独的类,但我们假设我们的示例是这样的),并且您想要测试该行为。您可以手动下载 XML 响应,并让您的测试从文件中读取数据并将其填充到响应中。然后你就可以确切地知道那里有什么数据,并且可以检查它是否被正确解析。或者,您可以实现 SAI_CurlInterface 立即从您的文件系统加载所有响应,但现有的实现绝对是一个起点。

      在我写这个答案的时候,@SAI_CurlStub@ 还不支持 cURL 多库功能,但我也计划在未来实现这一点。

      我希望这个存根对任何想要对依赖于 cURL 的类进行单元测试的人有所帮助。随意查看和使用这些课程,或者做出贡献,当然——毕竟它在 GitHub 上:)。此外,我愿意接受任何关于接口和存根的实现和使用的建设性批评。

      【讨论】:

        【解决方案5】:

        解决此问题的一种方法是将您正在使用的接口(在本例中为 curl_ 函数)替换为返回某些值的虚拟版本。如果您使用的是面向对象的库,这会更容易,因为您可以替换一个具有相同方法名称的虚拟对象(实际上,像 simpletest 这样的框架可以轻松设置虚拟对象方法)。否则,也许还有一些其他的魔法可以用来用假人覆盖内置函数。 This extension includes override_function() 看起来像您需要的,尽管这会添加另一个依赖项。

        如果您想在不使用虚拟版本替换 curl_ 函数的情况下进行测试,您似乎需要设置一个返回特定结果的虚拟服务器,以便您可以测试 PHP 的方式及其 curl扩展,处理该结果。要对其进行全面测试,您需要通过 HTTP 而不是本地文件来访问它,因为您的 PHP 依赖于 HTTP 响应代码等。因此您的测试将需要一个正常运行的 HTTP 服务器。

        顺便说一句,PHP 5.4 will actually include its own web server 会派上用场。否则,您可以将测试脚本放在您控制的已知服务器上,或者将简单的服务器配置与您的测试一起分发。

        如果您要实际使用实时服务器进行测试,这将不再是一个单元测试,而更像是一个集成测试,因为您正在测试您的 PHP 和服务器,并且两者之间的融合。您还将错过能够按需测试您的代码如何处理某些故障的机会。

        【讨论】:

        • 我想避免做一个真正的 HTTP 请求,以保持测试的快速。用虚拟数据替换 curl 函数将是最好的选择。不幸的是,这段代码将成为我正在构建的框架的一部分。因此,任何使用它的人都必须安装依赖项才能正确运行测试。
        • 也许有一种方法可以修改您的应用程序代码以允许您轻松地交换不同的 curl_ 函数(使用例如 call_user_func())。那么你的测试代码可以利用这个吗?
        【解决方案6】:

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

        【讨论】:

        • 这并不适用于所有环境。如果其他开发人员运行测试并且他们没有本地主机设置,则测试将因错误原因而失败。
        猜你喜欢
        • 2016-10-21
        • 2012-01-08
        • 2017-07-24
        • 2015-11-29
        • 1970-01-01
        • 2015-09-13
        • 2020-08-17
        • 1970-01-01
        • 2016-09-12
        相关资源
        最近更新 更多