如何对Google API的调用进行单元测试

时间:2011-01-31 11:41:25

标签: unit-testing zend-framework mocking phpunit google-api

我有以下方法,可以从Google Analytics中检索热门访问过的网页:

public function getData($limit = 10)
{
    $ids = '12345';
    $dateFrom = '2011-01-01';
    $dateTo = date('Y-m-d');

    // Google Analytics credentials
    $mail = 'my_mail';
    $pass = 'my_pass';

    $clientLogin = Zend_Gdata_ClientLogin::getHttpClient($mail, $pass, "analytics");
    $client = new Zend_Gdata($clientLogin);

    $reportURL = 'https://www.google.com/analytics/feeds/data?';

    $params = array(
        'ids' => 'ga:' . $ids,
        'dimensions' => 'ga:pagePath,ga:pageTitle',
        'metrics' => 'ga:visitors',
        'sort' => '-ga:visitors',
        'start-date' => $dateFrom,
        'end-date' => $dateTo,
        'max-results' => $limit
    );

    $query = http_build_query($params, '');
    $reportURL .= $query;

    $results = $client->getFeed($reportURL);

    $xml = $results->getXML();
    Zend_Feed::lookupNamespace('default');
    $feed = new Zend_Feed_Atom(null, $xml);

    $top = array();
    foreach ($feed as $entry) {
        $page['visitors'] = (int) $entry->metric->getDOM()->getAttribute('value');
        $page['url'] = $entry->dimension[0]->getDOM()->getAttribute('value');
        $page['title'] = $entry->dimension[1]->getDOM()->getAttribute('value');
        $top[] = $page;
    }

    return $top;
}

肯定需要一些重构,但问题是:

  • 您如何为此方法编写PHPUnit测试?

3 个答案:

答案 0 :(得分:6)

据我了解,通常您会希望将依赖项(Google客户端对象)注入受测系统(SUT,包含getData()方法的类)。

我总是看到专家使用构造函数注入 - 我确信这是一种更好的方法,因为它可以在前面清楚地识别依赖关系。但是,说实话,我似乎永远不能设计好我的物品,以便始终做到这一点。所以我最终做了setter注入。

这样的事情:

public function getClient()
{
    if (null === $this->_client){
        // $mail and $pass are stored somewhere, right?
        $clientLogin = Zend_Gdata_ClientLogin::getHttpClient($mail, $pass, "analytics");
        $this->_client = new Zend_Gdata($clientLogin);
    }
    return $this->_client;
}

public function setClient($client)
{
    $this->_client = $client;
    return $this;
}

然后在单元测试中,您创建一个$client对象作为您的实时$client的模拟,设置期望值,然后使用setClient($client)方法将其注入您的SUT如上所述。

明白我的意思?

答案 1 :(得分:2)

David Weinraub给你上半部分(如何设置你的类可以模拟),所以我将解决下半部分(如何构建模拟)。

PHPUnit提供了一个很棒的模拟工具和一个简单的API。传递用户和密码太简单了,无法在我的书中测试,所以我只是模拟查询和结果的处理。这需要Zend_Gdata和Zend_Gdata_App_Feed的模拟。

public function testGetData() {
    // expected input to and output from mocks
    $url = 'https://www.google.com/analytics/feeds/data?ids=ga:12345...';
    $xml = <<<XML
<feed>
    ...
</feed>
XML;
    // setup the mocks and method expectations
    $client = $this->getMock('Zend_Gdata', array('getFeed'));
    $feed = $this->getMock('Zend_Gdata_App_Feed', array('getXML'));
    $client->expects($this->once())
           ->method('getFeed')
           ->with($url)
           ->will($this->returnValue($feed));
    $feed->expects($this->once())
         ->method('getXML')
         ->will($this->returnValue($xml));
    // create the report (SUT) and call the method being tested
    $report = new MyReport();
    $report->setClient($client);
    $top = $report->getData();
    // check the final output; mocks are verified automatically
    $this->assertEquals(10, count($top));
    $this->assertEquals(array(
            'visitors' => 123, 
            'url' => 'http://...', 
            'title' => 'My Home Page'
        ), $top[0]);
}

以上内容将测试网址是否正确并返回Google预期的XML Feed。它消除了对Zend_Gdata类的所有依赖。如果你不在setClient()上使用类型提示,你甚至可以使用stdClass作为两个模拟的基础,因为你只使用模拟方法。

答案 2 :(得分:-1)

我的第一个倾向是告诉你,这一个函数getData是最讨厌和最丑陋的代码之一。你问的是如何对它进行单元测试。那么猜猜我的推荐是什么?重构。

为了重构此代码,您需要覆盖率测试

重构的原因很多:

  1. 对第三方框架的依赖。
  2. 对外部服务的依赖。
  3. getData责任太多了。

    一个。使用第三方框架登录外部服务。

    湾创建外部服务查询。

    ℃。解析来自外部服务的查询响应。

  4. 您是如何将代码与第三方框架和外部服务的更改隔离开来的?

    你真的应该看看Michael Feather的书。 Working Effectively with Legacy Code

    <强> [编辑]

    我对你的观点(扰乱者来了),是这个代码,你永远不会得到真正的单元测试。这是因为依赖外部服务。单元测试无法控制服务或返回的数据。单元测试应该能够执行,以便每次执行时它的结果都是一致的。对于外部服务,情况可能并非如此。 您无法控制外部服务返回的内容。

    如果服务中断,你会怎么做?单元测试 FAIL

    如果结果返回怎么办?单元测试 FAIL

    单元测试结果必须在执行之间保持一致。否则它不是单元测试。